Welcome to MSDN Blogs Sign in | Join | Help

By default, IIS 7.0 limits the maximum size of any request. Those of you who have implemented SharePoint 2007 on Windows 2008 are very familiar with this; specifically, modifications must be made to the web.config for your provisioned web applications setting maxAllowedContentLength to an appropriate value (the default is typically way too small). This is described fairly well in http://support.microsoft.com/kb/925083 (check the More Information section).

It's not obvious that this same limit will impact content deployment, but it does. Content deployment performs an HTTP upload of the exported content (packaged as CAB files) to the destination farm - even if the content deployment job is an inter-farm affair. As a result, if you ever find see messages in your event log complaining that the ContenDeploymentJobDefinition failed with an error of "The remote server returned an error: (404) Not Found.", you may just be running into this problem. Remember, you are uploading the exported content to the Central Administration (or an instance of Central Administration) on the remote farm. You'll need to increase the maxAllowedContentLength value for that web app, otherwise you'll get 404 errors (subcode 13).

ASP.NET 4.0 includes a new member on the HttpResponse class - RedirectPermanent. Baked in support for issuing HTTP 301's? Yay!

Have you ever gotten your system to a state that you absolutely love - all your apps and utilities loaded, Visual Studio instaled, etc. - and find that you need to load Potentially Deadly Beta Software v0.8? Have you ever cringed at the thought that loading such software runs the risk of screwing up hours of work getting your machine configured just right?

Enter disk2vhd. Those crazy SysInternals guys, they are wicked smart. This free (!) utility can convert the drive that you are currently booted in to into a VHD. Once there, you can do all kinds of fun stuff. Load it up in Hyper-V. Configure Windows 7/2008 R2 VHD boot with it. All sorts of stuff. Load Deadly Beta Software on it.

Just generally go crazy.

http://technet.microsoft.com/en-us/sysinternals/ee656415.aspx

 

When IIS 7.0 shipped with Windows Server 2008, one of the many new features included this new doodad called Kernel Mode Authentication. Enabled by default, KMA moved authentication operations performed by IIS out of user mode and into the kernel. This had tremendous performance benefits! Additionally, when KMA was enabled, IIS no longer used the application pool identity for Kerberos ticket decryption - instead, it would use the machine account. This greatly simplified single server installations, as you wouldn't need an SPN configured to perform Kerberos authentication to your web application if your web application used the same name as the machine.

Unfortunately, relying on the machine account for ticket decryption is entirely incompatible with web farms of any kind - SharePoint 2007 included. It's obvious why - SharePoint web applications running in a farm configuration do not use the machine name for their web application, they'll typically use a central DNS entry and host headers on each of the Web Front Ends. As a result, the ticket request from the browser client to Active Directory would pass an SPN that wouldn't have a match of any sort, causing a Kerberos ticket failure on the client. As a result, SharePoint 2007 environment deployed on Windows 2008 had to configure their web applications to use the application pool identity for ticket decryption if Kernel Mode Authentication was to remain enabled.

Due to a variety of factors, SharePoint 2010 will provision web applications with Kernel Mode Authentication disabled by default. This will give you the IIS 6.0 behavior of leveraging application pool identities for ticket decryption, and will generally simplify web application management.

Every SSP that is created in a farm is represented to the world by a virtual directory configured beneath the Office Server Web Services web application that's configured on every node within a SharePoint farm.

The Office Server Web Services web application is associated to the OfficeServerApplicationPool. If you popped open IIS Manager, you'd probably note that the OfficeServerApplicationPool executes under the NETWORK SERVICE account. Each SSP in the farm will be represented by a virtual directory configured beneath this web application; the virtual directory is named with the name of the SSP, so if your farm runs an SSP named SharedServices1, looking underneath the Office Server Web Services web application would reveal a virtual directory named SharedServices1, associated to an application pool with the same name. That application pool runs under the identity of the SSP service account it executes for.

The Office Server Web Services web application is bound to port 56737 and 56738. Sound familiar? You configured a half dozen custom-format SPNs (two for each member of your farm) on your SSP service account when you configured your farm for Kerberos authentication. Right?

I like things that break. I like new things. In the world of software, new things, also known as beta things, tend to break quite often, which made me and my Windows Home Server backups a little too familiar with each other.

Enter Windows 7 and Boot from VHD.

Now granted, I could've always dual booted, but a dual-boot system is sort of a big commitment. That disk space is gone, and there just isn't an easy way to get it back. With boot from VHD, if I need it, I dump it on my hard drive, if not, I can shuffle it off to larger stores without losing all my setup time. I can get it to a state I like, then copy that off for safe keeping, like after I've loaded Visual Studio 2010 Beta 1 on it, but before I've done something hideous to the OS.

Scott Hanselman has a post with a pretty decent guide that covers the basics of boot from VHD. Read all about it here.

Also, for the record, I never use any fancy Windows 7/Windows 2008 R2 install media tricks to install an OS on a VHD. Download the Windows AIK and be introduced to the joys of ImageX. With ImageX, you can select the Windows install image (from the source media install.wim), and lay the bits down on the VHD without having to run through the full installer. First boot, it's very similar to a sysprep experience.

Windows 7 FTW!

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.

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. :)

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.

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.

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. :)

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!

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...

 
Page view tracker