Welcome to MSDN Blogs Sign in | Join | Help

Why User Testing Is So Important

I recently placed an order with a company for something my wife and I had been discussing quite a bit. We were quite excited to finally order this particular item. I was duly impressed when, about an hour after I placed my order, I actually received an automated phone call from the company informing me that the order had been placed and asking me to confirm my details associated with the order, to prevent the possibility of someone signing me up for this particular service without my knowledge. A nice touch in the age of identity theft.

A week went by, and I had rearranged my entire weekend to be able to accept delivery on Saturday. I waited. And waited. And no one ever showed up. Frustrated, I went downstairs to the home office to pull out the order confirmation email that I had recieved earlier in the week, so I could dig out the customer service phone number and the order number.

The email, with a subject line of "Order Confirmation for Order #1234567", surprised me a bit. The top contained the standard order confirmation stuff - a restatement of the items I had ordered, the subtotal, tax, expected delivery date, etc. At the bottom of the email, however, I was surprised to find the following paragraph:

"We're sorry, but due to an error while processing your credit card, your order cannot be fulfilled at this time. If you do not contact us within 10 days, we will cancel this order."

This sentence was actually crammed between a sentence thanking me for my order, and a block of legalese.

 I was flabergasted.

After a brief chat with their customer service folks, it turns out that I must have mistyped my credit card number. This raises three issues, for me:

  1. People have been doing realtime processing and validation of credit cards for twenty years or more. It's a commodity skill. Why didn't the user interface catch that mistake? Even something as simple as a mod-10 check would have found this particular error.
  2. Why would the email not be titled something such as "Order Not Processed - Please Contact Us" or something similar? Why make the customer dig through the response to determine their order status, when the initial subject indicates that all is well?
  3. Given the companies obvious infrastructure for placing order-driven outbound calls (such as the one I received verifying that it was actually me that placed the order) why didn't they leverage that same system to inform me of my mistake?

The issue here is that they obviously never user tested their confirmation emails through all the possible use case scenarios. Granted, I should have read the email end to end, but it also should have been much more obvious that there was something that was going to prevent the succesfuly completion of my order. It shouldn't be this difficult to give someone money.

So, the lesson here is to user test as much as you possibly can - especially those elements that require the user to perform some action. And especially when it might interfere with your ability to accept money from someone.

Posted by Bryan Porter | 4 Comments
Filed under: ,

My Favorite Interview Question

I've given plenty of interviews in my day. Almost all of them have been for highly technical positions; typically software development. Of all the interview questions I've ever asked, this one is my absolute favorite:

 "Write a function that can take the derivative of a single variable polynomial of any order. Perform this operation using the smallest number of operations possible."

Now, normally the people freak - and that's okay. They hear "derivative" and start flailing around, their brain short circuiting from the stress of the interview situation, until they aren't entirely certain if a derivative is a mathematical construct or the pet name of a certain species of spotted owl. I would typically continue on to give them an example of a taking a derivative, just to refresh their memory. I also tell them that the function can take whatever input they feel is neccessary. I then give them 15 minutes to work on this.

No one has ever come close to answering the question right. Well, one guy did, and I hired him. Of the rest of the people that I hired, they all got close - but here's the trick. The question wasn't about finding the right answer. The question was about finding out how your brain worked by a) stressing you and b) requiring you to think logically under that stress.

 Anyone care to take a stab at it? Brownie points for the first person to get it right. :)

Posted by Bryan Porter | 5 Comments
Filed under: ,

You Never Know What You Don't

I was once asked by a colleage at a previous employer what skills would be required to implement and maintain a piece of software we sold. After thinking about this a bit, I wrote back with three points:

  • Software Development Experience
  • Motivation
  • Interest

The answer I received in reply isn't fit to print.

A well planned project includes a list of tasks that need to be accomplished in a given order, and sets expectations as to the timeframe those tasks should require. It's an artform or sorts. Some tasks come in quicker than the estimate, some longer, but the goal of project management is to manage the margins. Basically, to keep tabs on where everything is and where everything is trending so that you can adjust your long term plans before you get so deep into it you'll never ship.

In this particular case, this software package required both infrastructure/operational elements and development elements. Infrastruture and operations were pretty much the same as most apps - database servers, network connectivity, load balancers, etc. The development elements were also pretty much the same for any customization effort - knowledge of the API would of course be helpful, but isn't strictly required. You'll pick things up as you prepare for the project, and the timeline assumes someone is new to the product.

The answer this person wanted was that we needed people with experience deploying, customizing, and supporting the product. The challenge was, the client in question had never deployed, customized, or supported the product. The only way the client was going to get those skills was by doing, with someone who had done it before walking behind everyone, prodding them along and keeping them inside the margins.

That's just how products are adopted. Sometimes you get an opportunity to send a bunch of people to a training class, and if you can swing it, that's great - a definite help. But training classes and the real world seldom intersect in a way that improves delivery - if anything, they serve to highlight to the person the fundamentals of the product they are deploying or customizing. You know know what you don't know, and know what questions to ask. This is also the goal of project preperation, where you plan out activities in detail and send people off with manuals and documentation in the bags. In many instances, training represents a different sort of documentation delivery.

And for the record, I'm not knocking formal training. Consider this - in college, you bought this big expensive text book. If you were dilligent, you could have read the text book end to end, taken the exam, and passed. You could have taught yourself. Or, you could take your textbook and attend class, where your teacher explains the things in the text book to you, and shares his or her own personal experiences. Now product training and documentation may not have the wide gap in knowledge adoption that say, a calculus class and a calculus text book might have, but the same thing goes.

Posted by Bryan Porter | 1 Comments
Filed under: ,

Give

When I was about 10 years old, my parents finally relented and bought a computer. Up to that point, my passion for computers had been sated by the occassional visit to my cousins house, where I would bang frantically on the keyboard of an old Apple IIe until I was pulled, kicking and screaming, from the room.

It wasn't that my parents didn't want me to have a computer - the truth is, we were flat broke. Mom was a nurse. Dad had a mathematics degree and couldn't get a job. So Mom worked at a nursing home and Dad managed a copy shop. If you can imagine that - the most qualified copy machine operator in the history of the known universe. Dad has (and still has) an astounding mind, but he needed a job that had the key benefit of providing a pay check, and so that's what he did.

We lived in Woodson Terrace, a small municipality in north St. Louis county, in Missouri. If you run through Woodson Terrace now, it's definitely not the best place in the world to live. Not terrible, but definitely not great. At the time though, it was a bit better than not terrible, and much closer to ok. Regardless, we were decidely lower-lower middle class, and a few thousand bucks for a computer just wasn't in the cards. Being ten years old, that didn't really occur to me. I begged. I pleaded. And I did something that I never should have done - I lied.

So desperately did I want a computer, I actually told everyone at school that my Mom had surprised me with one, and how I was doing all this cool stuff with it. The lie was fantastic. Until parent-teacher conferences came around. There I was, sitting on the chairs in the hallway outside the classroom, not a care in the world, when I heard my teacher tell Mom, "Your son certainly does love computers. He is always telling everyone all the exciting things he's doing with them.". My mother replied, "But we don't own a computer." "Oh", the teacher said, and then walked into the hallway, gave me an inquisitive look as she reached for the door handle, and closed the door behind her. I never heard the rest of the conversation in the classroom, but I was so frightened when my Mom came out, that I didn't say a word until we got home.

A few days later, my parents told me that we could get a computer. How they came up with the money, I do not know. But we picked up a 386SX 16 with 1MB of RAM and a 40MB hard dive. I was in heaven. Except that, I didn't know how to do anything with it. And, I didn't have a way to know how to do anything with it. Mom and I tried the library, but there wasn't much there. Exasperated, I spent my nights and weekends making qbasic do things it was never intended to do. I knew I wanted to be a programmer, but I just didn't know how to know.

There was a fast food restaurant behind our house, anchoring a strip mall that ran a two block length of Woodson Road, the main thoroughfare cutting the city of Woodson Terrace in two. I used to ride my bike in the parking lot there, and from time to time I would stop in and buy a soda. One August, when it was painfully hot and humid outside, I went in to buy a soda and sat down next to the door, enjoying the relief from the heat and relaxing in the air conditioning. There was an older fellow a few tables over who was eating dinner and flipping through a copy of a book about COBOL. I asked him if he was a programmer - he said, "Why, no, but I am a software engineer." Not getting the distinction, I told him quite flatly that I wanted to be a software engineer when I grew up. "Really? Why is that?" he asked. I told him the truth - that I had no idea. I had always liked computers, liked figuring out ways of making them do things, and that my grandpa was an engineer for McDonnell-Douglass and had written all sorts of programs. I told him about some of the things I was doing in BASIC, and he smiled. "Tell you what, my name is Ed, and if you come back tommorow I'll have something for you."

I came back the next day around the same time, and he handed me two books. The first was a book on Microsoft QuickBASIC 4.5, and the second was a book on C and C++. Both books included compilers and tools in a set of 3.5" floppies taped to the back of the book. "I'll need these back.", he said. "But you can borrow them for a while."

Ed had given me the keys to the kingdom. I took the books, and I read them end to end. I worked through all the examples printed in the books and then started coming up with new and exciting ways to extend them. I wrote a grocrey management program for my Mom, and a baseball score keeping program for my Dad. I even played around with TSRs, all sorts of DOS goodness. I was in heaven.

Now, almost 20 years later, I work for Microsoft, and I see this place as the fulfillment of my childhoom dreams. Ed handed a sweaty kid two books, and changed my life forever. I get to work with some of the smartest people in the world, and I absolutely love every single second of it.

So if anyone ever asks if they can borrow a book, let them. If they don't return it, you're out a few dollars, but by taking the chance, you are giving someone the means and opportunity to change their life. Remember that. You are helping someone latch on to a better future by giving them the tools to hang on.

Posted by Bryan Porter | 1 Comments
Filed under: ,

Self-Policing of Content

One of the most common questions I hear from Human Resources folks surounds content management of My Sites and Team Sites. Specifically, managing innapropriate content. There are lots of things in SharePoint that can help restrict people from doing things - security, workflow, blocked file types, and more - but it becomes a bit unclear when it comes time to restrict people from placing innapropriate content in the environment. Take images for example. It would be foolhardy to make, say JPEGs a blocked file type, but filling up your content databases with pictures of peoples weddings is something you really don't want to see happen. What to do?

My favorite technique for combating these types of issues is to implement a global feedback link. These links serve as a listening system for innapropriate content - implemented as a delegate control, they can be activated at the web application level, and statically linked to a document library where additional details regarding the policy violation can be collected. It's a great way of using SharePoint to manage SharePoint, is pretty simple to implement, and leverages the collective power of your audience to self manage policy violations.

Posted by Bryan Porter | 0 Comments
Filed under: ,

I hang my head, and I feel shame.

Wow. My last post was when? Scott Hanselman posts 18 times per day, while in Africa, and I haven't posted since August?

I hang my head, and I feel shame.

Well, in my own defense, I have spent a great deal of time over the last four months looking at the inside of airports. Ever been to Denver International? It's gorgeous. Kansas City International? Exceptionally functional. Not pretty, but it works, and it works well.

Continuing my, series, here are a few more myths about SharePoint I'd like to burn down.

#3 You shouldn't have a content database larger than 50GB

People have misinterpreted a lot of guidance on this topic over the years. Implying that SharePoint shouldn't have a database larger than X size implies that there is some pre-determined upper bound to the size of a SQL Server database, which just isn't so. A SharePoint content database can scale to whatever size your SQL Server infrastructure can support, which means that if you've got some crazy 64 processor SQL Server with 512GB of RAM, you can have yourself a big ole' honkin' content database.

Now, before everyone starts building out huge SQL Server infrastructures, let's take a close look at that SLA you agreed to. You know, the one that says you'll be able to restore the system within four hours of an outage? Now, go talk to your storage management people. What do they say about how they are backing up your databases? Mirror SAN? Same building? Going to tape?

The deal with content database size is all about the rapidity with which you can recover the database. Personally, I'm nothing but happy if I can keep the database to 50GB or less. Normally, this is very achievable with a sufficiently developed site taxonomy - but more on that later.

More to come - and this time, you won't have to wait four months. I hope. :)

Posted by Bryan Porter | 1 Comments
Filed under: ,

Dispelling SharePoint Myths

I've heard numerous myths about SharePoint. Some were scary. Others, funny. Regardless, for your reading pleasure, I'm going to spend the next few posts talking about some of my favorites, with an aim to dispell them.

#1 SharePoint Is More Than An ASP.NET Application

SharePoint is a wonderful product. But - and this is important - SharePoint is an ASP.NET application. Repeat after me. SharePoint is an ASP.NET application. Keep repeating that until you believe it.

I can't tell you how many times I'm asked if there is some "secret sauce" involved in turning an IIS web application into a SharePoint web application. There isn't. Let me break it down for you.

In SharePoint 2003, when SharePoint extended an IIS web application, it registered a custom ISAPI filter to intercept page request handling. This, in turn, would be handed to the ASP.NET page processing pipeline for further manipulation before finally returning the requested page or error to the client. The ISAPI extension was a bit of magic, but didn't do anything that a normal ISAPI extension couldn't do. There wasn't anything special about it, other than the fact that it was written for you. That was it.

The ISAPI extension did pose some problems, though. The ISAPI extension had to be the first ISAPI extension in the list of ISAPI extensions executed for a given web application. Getting that ISAPI extension out of order could cause problems if other, "downstream" ISAPI extensions processed things funny. As a result, for SharePoint 2007 the ISAPI extension was removed and a wildcard application map was added that mapped all requests to IIS for a SharePoint extended web application to the ASP.NET page processing pipeline.

Normally, only files that end in a specific extension are mapped to the aspnet_isapi DLL (and thus the ASP.NET page processing pipeline). Things like .aspx, .config files, etc. For a SharePoint extended virtual server, a wildcard mapping is added that maps everything to the ASP.NET engine. In the web.config file for a SharePoint site, two HTTP handlers are added to the ASP.NET engine that enable SharePoint to process the request like a SharePoint request. These two HTTP modules are the SPRequest module and PublishingHttpModule.

Take a look at the web.config file for a SharePoint site. In the HttpModules section, you'll see two entries that look like this:

      <add name="SPRequest" type="Microsoft.SharePoint.ApplicationRuntime.SPRequestModule, Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
      <add name="PublishingHttpModule" type="Microsoft.SharePoint.Publishing.PublishingHttpModule, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />

These two modules obviate the need for the ISAPI extension. This is what enables SharePoint to serve content that isn't stored on the filesystem, like documents, images, pages, etc. These modules intercept the ASP.NET page request and retrieve that data from the SharePoint content database(s) associated with the web application, instead of letting IIS do the heavy lifting by grabbing it from the filesystem.

So, when SharePoint "extends" an IIS web application, what it's really doing is adding a wildcard mapping to the standard ASP.NET processing DLL. All requests get handed to aspnet_isapi.dll, the same DLL that ships with ASP.NET proper and the same DLL that gets a request for one of your normal ASPX pages. SharePoint then drops a web.config file in the IIS web sites directory that adds a few HTTP modules to process the request further, retrieving things from the database, and serving SharePoint content. That's it. That's all there is. Tada. You're done.

#2 You Can't (Or It's Hard To) Host Non-SharePoint Sites Beneath A SharePoint Web Application

See item #1. SharePoint is an ASP.NET application. The same rules apply to all ASP.NET applications. Many people encounter challenges trying to put their own, non-SharePoint ASP.NET application in a virtual directory beneath a SharePoint site. It's broken, you get funny errors, and the like. What's causing this?

Normally, it's just a byproduct of the way web.config files work. Web.config files inherit from each other. So, if you have a SharePoint web application, you create a virtual directory beneath that web application, drop your web.config and such for your custom ASP.NET application in there, what's happening is your web.config file for your custom app is inheriting all the settings specified in the parent web application. Since your app probably doesn't know about many of the SharePoint DLL's, you get a bunch of funny errors because your ASP.NET application is trying to load SharePoint's HTTP modules, configuration sections, and more. The answer? Judicious use of <clear/> statements.

In ASP.NET, all web.config files inherit from something (save the top config file, machine.config). So, when you create a new web application and put your site there, it works, because it's inheriting from the systems machine.config, and you are used to that. Even if you didn't know that that is what it was doing, your app behaves as you'd expect it to because it's grabbing config values that you expect it to have. Ever wonder why you can just type <system.web> in your config file, and ASP.NET knows what to do with that? Believe it or not, there isn't any special processing that the ASP.NET engine does to "know" about a <system.web> config section in a web.config file. The configuration handler for a <system.web> section is defined in the machine.config file on the system. This is the same syntax and approach that you would use to declare your own custom configuration section, and the same syntax and approach that SharePoint uses to defines its custom sections (i.e. the SafeControl section). For more information on custom configuration sections, see my MSDN article "Configure This: Parameterize Your Apps Using XML Configuration In The .NET Framework 2.0".

Back to your ASP.NET application that you want to live beneath a SharePoint site. Your app is inheriting a bunch of settings that doesn't make sense for it - things like SafeControl sections, authentication and authrorization sections, site map providers, connection strings, and more. The answer? Take a look at the parent SharePoint web.config, and use <clear/> and <remove/> statements in your web.config to get rid of the settings that you don't need or want. Tada.

A note of caution: if you <clear/>, for example, the connection strings section, know that you are not only clearing the connections strings that SharePoint defined, but you are also clearing the connection strings that the machine.config file defined. The connection strings section has inherited settings all the way to the top, and when you clear it, you clear all inherited configuration values. This might not matter to you, but it's something to watch.

That's it for now. I'll post more later, as I've got myself a ton of these!

Posted by Bryan Porter | 1 Comments
Filed under:

ICallbackEventHandler

ASP.NET AJAX is a piece of fundamentally enabling technology for developers; everything from partial page rending using UpdatePanels to the cross-browser compatible script library enable you to provide engaging web solutions while minimizing effort and time. Personally, I think the ASP.NET AJAX libraries are something that every software engineer building solutions for the Web should be be intimately familiar with.

While ASP.NET AJAX may be a wonderful, wonderful thing, it is helpful to remember some of the other ways we can make use of script on the client to achieve fast, friendly sites while minimizing post-backs. ICallbackEventHandler, an interface added to ASP.NET way back in the 2.0 days, is a wonderful solution. Not only is it relatively simple to implement, it allows you to keep everything nice and tidy inside your server control assembly. Way back in 2005, Dino Esposito of MSDN Magazine fame (amongs other claims to fame) wrote an excellent article describing ICallbackEventHandler in all its glory.

Now, I spend a lot of time in developing for SharePoint. SharePoint has some wonderful controls that produce a great experience for the end user. One of the controls I love the most is the ubiquitous people picker control. Type in a name, click a button, and voila - SharePoint automagically figures out if the name you typed matches a user account it knows about, and if so, formats the user account name to show that it knows. All without a postback. This is great when doing things like assigning users to groups in SharePoint, as your not constantly switching back and forth between one page and the next, creating a disjointed and post-back happy user experience.

How does the people picker control in SharePoint do this? Does it host the control in an UpdatePanel, or use a Web Service to process the data you type? Why no - in fact, it uses ICallbackEventHandler to achieve its functionality.

And so, for this article, I'll show you how to write a control that's inspired by the people picker control in SharePoint, and I'll use ICallbackEventHandler to do it. Along the way, you'll see the benefits of embedded javascript and CSS resources for server control developers and how a few WebResource attributes can really simplify our lives. When all is said and done, you'll have a control that can verify that a particular persons username exists in the membership store for the current sites configured membership provider in a nice, attractive, and reusable way.

Let's get started!

Assumptions

For this walkthrough, I'm assuming that you're using Visual Studio 2008. Realistically, the technique described here can be done in Visual Studio 2005; the attached sample code consists of a Visual Studio 2008 solution. If I get enough requests, I'll gladly provide a VS 2005 sample solution.

Anatomy of the Control

For our control, we'll keep the user interface relatively simple. A styled textbox with a styled button adjacent to it should do the trick. The end user will type the name of the user he wishes to verify the existence of in the textbox; when he clicks the button, we'll perform a callback to the server through script, executing our callback handler which will grab the value of the textbox and verify the existing of a user with that name in the membership provider. We'll then return a result to the client which will be processed by a script method of our choosing; this script method will update the style of the textbox to indicate to the user that the user was found; otherwise, the script will leave the textbox as it is. You could always embellish the solution with a few more bells and whistles - perhaps instead of relying on the style of the textbox to indicate success or failure, maybe throw a little status icon next to the textbox itself. That I'll leave to you.

People Picker Render Example

The image on the left shows what are control will look like. We'll achieve this by producing a custom composite control. Let's start our little sojourn by writing the JavaScript that will make our solution shine.

The Script

To start, crack open Visual Studio and pick the ASP.NET Server Control project template. My project is named PeoplePicker.Controls, so I'll assume you'll name yours the same as well. Now, add a new JavaScript file to the project and name it PeoplePicker.js. Select the JavaScript file and change the Build Action to Embedded Resource. This is important, as we'll discuss later.

Open your newly created JavaScript file, and we're going to add two functions to this file. The first function, verifyUserName(), is the function that will be invoked after our callback has executed on the server. This is where we get our results back, and do some processing. The second function, makeEditable(), is going to be called whenever someone double clicks on our text box. If verifyUserName() receives a positive response from the server, we're going to style the contents of the text box by making the text it contains underlined. We'll also make the textbox read-only, further indicating to the user that we were able to find the user they specified. If the user double clicks in the textbox, we'll undo these changes to allow the user to specify someone else.

function verifyUserName(result, context) {
     // result contains a string value provided by our callback function on the server
     // context contains a reference to our text box control

     context.value = result;
     context.readOnly = true;
     context.style.textDecorationUnderline = true;
}

function makeEditable(textElement) {
     var textBox = document.getElementById(textElement);
     textBox.readOnly = false;
     textBox.style.textDecorationUnderline = false;
     textBox.select();
}

You'll notice that in verifyUserName, we set the value of the textbox (rememer, context points to our textbox on the web page) to whatever happens to be in the result variable. We do this because our server-side callback sends the name of the user found back to the client. Now, this implementation doesn't have any facility for searching - if your username is john and you type in joh, no dice - but using this technique, it'd be a snap to add something pretty cool.

Now, on to our stylesheet. Add a new stylesheet to the project named, ahem, PeoplePicker.css. Just like the JavaScript file, set the Build Action of this file to Embedded Resource. Now, open it up if it's not already open.

.peoplepicker-input
{
     border: 1px solid black;
     text-decoration: none;
     width: 150px;
     height: 17px;
}

.peoplepicker-button
{
     border: 1px solid black;
     width: 50px;
     height: 21px;

These two styles are all that we'll have - give a little shine and polish to the textbox and button is really what we're looking for. Of course, you should feel free to edit these to suit your own tastes. When it comes to art direction, you don't want my advice. ;-)

That leaves just one thing left to build! And that's the server control itself. But before we get to that, a quick word about web resources (and all that Embedded Resource action we've been seeing...)

A Quick Word on Web Resources

In order to expose our script and stylesheets to the client, with a minimum of muss and fuss, we're going to add two assembly-level attributes to our projects AssemblyInfo.cs file. This file is normally found underneat the Properties folder of our project. If you crack that file open, you'll see a ton of attributes applied to the assembly already - this is where most people like to keep their assembly attributes, and Visual Studio places all the assembly attributes it applies in here by default. Realistically, you can put these assembly attributes anywhere, but I prefer to keep them all together.

[assembly: WebResource("PeoplePicker.Controls.PeoplePicker.js", "text/javascript")]
[assembly: WebResource("PeoplePicker.Controls.PeoplePicker.css", "text/css")]

Briefly, what you are doing here is you are attaching metadata to the assembly that tells the ASP.NET runtime about two embedded resources in your assembly that you want to be surfaced on the page through a web resource link. Now, the exact form of the link is up to you - nothing magical happens without you making it happen - and we'll define how to surface these resources in our server control proper.

For more information on the power and flexibility of WebResources, take a look at this fine description on MSDN.

The Server Control

We're almost there. Now, to write the server control. Add a class to your project (or rename a class that Visual Studio created by default) named PeoplePicker. Crack open the newly created PeoplePicker.cs file, and change add the following code:

[ToolboxData("<{0}:PeoplePicker runat=server></{0}:PeoplePicker>")]
public class PeoplePicker
     : CompositeControl, ICallbackEventHandler
{
     private TextBox m_userName;
     private Button m_checkUserNameButton;
    
     private string m_callbackResult;

     public PeoplePicker()
          : base()
     {
          m_callbackResult = string.Empty;
     }

What we've done here is declared that our class inherits from CompositeControl (one of the server control base classes) and implements the ICallbackEventHandler interface (more on that later). We've defined a few private variables to hold references to our child controls we'll be rendering to the client, as well as a string that will contain the results to send to the client as a result of the client callback. In our constructor, we just initialize our string to string.Empty, so that we don't ever send a null back to the client.

protected override void OnInit(EventArgs e)
{
    // Register Client Script & Style Sheets
    if (this.Page != null)
    {
        this.Page.ClientScript.RegisterClientScriptResource(typeof(PeoplePicker), "PeoplePicker.Controls.PeoplePicker.js");

        System.Web.UI.HtmlControls.HtmlLink cssLink = new System.Web.UI.HtmlControls.HtmlLink();
        string cssUrl = this.Page.ClientScript.GetWebResourceUrl(typeof(PeoplePicker), "PeoplePicker.Controls.PeoplePicker.css");

        cssLink.Href = cssUrl;
        cssLink.Attributes.Add("rel", "stylesheet");
        cssLink.Attributes.Add("type", "text/css");

        this.Page.Header.Controls.Add(cssLink);
    }

    base.OnInit(e);
}

Here, we finally get to make use of our WebResource marked embedded stylesheets and JavaScript files. The JavaScript file is the easiest to deal with - simply use the RegisterClientScriptResource method of the ClientScriptManager associated with Page object hosting our control. The stylesheet is a little more complicated, but not by much. We simply construct an instance of the very handy HtmlLink control and use the ClientScriptManager object to generate a URL to our WebResource - essentially, the same thing that ClientScriptManager does for script files, but we do the dirty work ourselves. Once we've got the URL to our stylesheet resource from the ClientScriptManager, we set the Href property of our HtmlLink instance to point to our stylesheet URL, add a few attributes to make sure the link is interpreted properly by the browser, then add the HtmlLink control to the control hierarchy of the hosting Pages' header.

Tada! We've injected script and styles with nary a funky call to HtmlTextWriter. I think this approach is a bit more maintainable to boot, and if you read the link I provided earlier to the MSDN article describing all the fun things you can do with WebResources, you'll also know that you can you use this same facility to provide localized scripts to your web clients. Pretty fancy, if you ask me.

protected override void CreateChildControls()
{
    base.CreateChildControls();

    m_userName = new TextBox();
    m_checkUserNameButton = new Button();

    m_userName.CssClass = "peoplepicker-input";
    m_checkUserNameButton.CssClass = "peoplepicker-button";

    m_checkUserNameButton.Text = "Check";

    // Controls won't have ClientID's generated until *after* they are added to the control
    // collection.
    Controls.Add(m_userName);
    Controls.Add(new LiteralControl("&nbsp;"));
    Controls.Add(m_checkUserNameButton);

    string js = String.Format("javascript:{0};{1};{2}; return false;", "__theFormPostData = ''", "WebForm_InitCallback()", this.Page.ClientScript.GetCallbackEventReference(this, "", "VerifyUserName", m_userName.ClientID));

    m_checkUserNameButton.OnClientClick = js;
    m_userName.Attributes.Add("ondblclick", string.Format("return makeEditable('{0}');", m_userName.ClientID));
}

Now, if you've ever written a composite control before, this all looks pretty straightforward.  But what's that mess in the line we build the JavaScript call used by our button? __theFormPostData = ''? WebForm_InitCallback()? Huh?

Well, here's the trick. By default, the state of your controls on the page don't get sent back to the server with the callback request. WebForm_InitCallback() is actually called early in the page render lifecycle, and so normally when your callback on the server would fire your textbox control would contain nothing. Here, we fix that - althought in a sort-of-workaround way, but hey Dino Esposito used this techinique in his MSDN article, and if its good enough for Dino, it's good enough for me. By clearing the post data variable and calling WebForm_InitCallback() again, we collect any values that have changed in our server controls, and that gets sent back to the server with our callback request - enabling us to get at the value of our client-side textbox without jumping through hoops.

Okay. Next possibly confusing part - this.Page.ClientScript.GetCallbackEventReferencce... yada yada yada. The ClientScriptManager associated with our Page object knows all about ICallbackEventHandler, and in fact provides us a method that will give us the script needed to invoke the callback method on the server. In order to do this, it needs to know the control for the callback, any arguments to provide the callback (we don't need any), the name of the client-side function to invoke when the callback is complete, and a context value.

Now, remember, way back when we were writing the script for this control, we noted that we get a reference to our textbox control passed to use in the context variable of our script callback? This is where that happens - we pass to our callback script the ClientID of the textbox control, which we can use from script to reference the control on the page. Since someone could potentially add multiple instances of our control to the same page, doing this frees us from having to keep track of which instance of our control on the page to mess with. We just use context, and we're done with it.

And to be honest, that folks concludes the most interesting portion of our control. Pretty simple, eh? But for completeness's sake, here's the rest of the code.

protected override void Render(HtmlTextWriter writer)
{
    EnsureChildControls();
    base.Render(writer);
}

#region ICallbackEventHandler Members
public string GetCallbackResult()
{
    return m_callbackResult;
}

public void RaiseCallbackEvent(string eventArgument)
{
    System.Web.Security.MembershipUserCollection users = System.Web.Security.Membership.FindUsersByName(m_userName.Text);

    if (users.Count > 1 || users.Count == 0)
        m_callbackResult = string.Empty;
    else if (users.Count == 1)
    {
        System.Web.Security.MembershipUser u = null;

        foreach (System.Web.Security.MembershipUser mu in users)
        {
            u = mu;
            break;
        }

        if (u != null)
            m_callbackResult = u.UserName;
    }
}
#endregion

In our overriden render method, I just make sure that our CreateChildControls method has been called by calling EnsureChildControls(), and in our two ICallbackEventHandler methods I do the relatively simple work this control set out to do - find a user in the configured Membership provider. Now, when it comes to ICallbackEventHandler it's important to note that RaiseCallbackEvent is the method that gets called on your callback, and GetCallbackResult is the method that gets called to return a value to the client. This is why we use a private member variable to hold the result we return to the client. In RaiseCallbackEvent, I check the membership provider for the username - conveniently stored in the Text property of our m_userName TextBox reference, thanks to our call to WebForm_InitCallback() - and if I find no matches, or more than one match, I return an empty string - which will cause our client script to clear out the contents of the textbox. If there is one match, I grab the username of that match and use that as my return value.

Conclusion

The end result - username checking without postbacks, without Web Services, and more.

Now, people can get ICallbackEventHandler crazy. I'm certainly not advocating that, just as I'm not advocating people abandon ASP.NET AJAX. You can call Web Services - real, live web services, complete with generated JavaScript proxy classes - from script in a cross-browser way. Think about the power there. But sometimes, that's a lot more than you need, and sometimes, you want something small, light, and quick to implement, that you can wrap up nice and tidy in a control library. When your needs are limited, ICallbackEventHandler can do the job for you. Heck, it's used all over the place in SharePoint.

And if its good enough for the SharePoint product team, or its good enough for Dino Esposito, it's good enough for me. ;-)

Man, that list is starting to get long...

Posted by Bryan Porter | 0 Comments
Filed under: , ,

Attachment(s): PeoplePicker.zip
 
Page view tracker