Welcome to Catalyst Blogs Sign in | Join | Help

The true power of the ASP.NET SQL membership infrastructure is fully felt as a triumvirate: authentication, authorization, and profiles.  Until now, the Hybrid Provider has only manifested two-thirds of this possible functionality; indeed, a "biumvirate."  Allowing SharePoint authentication and profiling simultaneously against AD and SQL has been the extent of what it can do.

But now I'm very happy to finally bring the Hybrid Provider full circle in its third release, incorporating the final piece of the membership puzzle: authorization - implemented by the Hybrid Roller.

The Hybrid Roller does its thing for authorization in the same spirit as the Hybrid Provider and Roller do their thing for authentication and profiling, respectively: allow both AD and SQL groups to be used for SharePoint authorization.  The reason the Roller has been so long in its coming is because I always ended up being able to get away with using SharePoint groups to do authorization in my portals.

Rarely did existing AD groups map exactly to what I needed in SharePoint; creating new groups (regardless of "what" these groups were) was easier then leveraging and aggregating AD groups.  And since a permission best practice in SharePoint is to have only groups in permission roles (not loose users), we would always create one SharePoint for each role definition, and add whomever we need to that group.

However, as more and more SharePoint intranets and extranets started to use the Hybrid Provider to manager their users, I decided to move forward and implement a role provider so that enterprises who have an intense AD forest already set up don't need to rely on reinventing the wheel with SharePoint groups.

Also, introducing SQL groups into the mix gives us the ability to define a more explicit group schema.  In other words, your organization can maintain group membership according to any requirements, naming conventions, or jargons in place by customizing the SQL membership database.  And by storing authorization information in a database, we can easily provision and scale new and existing SharePoint (or any ASP.NET) applications and bring users, groups, and profiles along for the ride seamlessly.

From a technical perspective, the Hybrid Roller works the same as the Hybrid Provider.  It has member providers for SQL and AD that it wraps and calls when appropriate.  By "appropriate" I mean a few things.  First of all, it always "tries" SQL first, since AD calls (especially groups) are expensive and COM-y; we don't want our external users (who are indeed usually clients, partners, or consultants) to wait while we burn calls to AD!

Secondly, and again like the Hybrid Provider, the Roller considers AD to be read only.  Therefore, calls to methods such as "DeleteRole" and "AddUsersToRoles" simply do not invoke the Active Directory role provider.  Essentially, we want to make sure to only make AD calls when absolutely necessary. 

Let's dig into these member providers a bit.  As with it's two big brothers, the Roller uses the out-of-the-box SqlRoleProvider class to implement the SQL side of things.  This provider really only needs a connection string and an application name, which are already provided by the Hybrid Provider and are wired up automatically during installation.

For AD, I hit a wall.  Indeed, there is a WindowsTokenRoleProvider class that allegedly does for groups with the ActiveDirectoryMembershipProvider does for users.  However, I remain unconvinced.  I mean, from what I've read it appears to be what User.IsInRole calls behind the scenes; I know this method works.  However, for the life of me, I could not get it to work in SharePoint!

Now I hate giving up, but compared to the authentication and profiling, authorization has the simplest configuration, so there's not that many settings to get wrong.  I was also turned off because a description of this provider on MSDN seemed to imply that it only did local Windows groups, not AD.     

This didn't seem to be very worthwhile to me, and it was a possible case of programmers just not being very clear & concise writers (I'm guilty too, I know).  But I gave up anyways and instead wrote my own AD role provider, named "Microsoftively" the ActiveDirectoryGroupProvider. 

This puppy actually implements many more methods of the base RoleProvider class than the WindowsTokenRoleProvider.  SharePoint only uses a few of them, but since I had a good base provider going, it wasn't much a stretch to implement a few more methods. 

And speaking of implementation, there are two important things to point out.  First of all, my AD group code is recursive.  To have true custom authorization working, especially in a place like a SharePoint extranet, we need to make sure that all of a user's permissions shine through.  So if an AD group is given rights to a site in SharePoint, and you are in a group that's in a group that's in a group that's in this group, you had better be able to browse there!

Secondly, is caching.  Since these AD calls (like I said before, especially group calls - and like I'm saying now, ESPECAILLY recursive groups calls) are expensive, we want to cache the results to make sure subsequent pages load much faster.  The role provider infrastructure actually has some out-of-the-box support for caching results in cookies, which, again, I would have happily leveraged if it appeared to me as though it were a no brain-er.

But I didn't observe any differences in call times when I tried this method.  And, umm, cookies?  Really?  Are we still using cookies when we have technologies like ASP.NET and Silverlight out there?  No cookies.  Cookies are fattening.  So, again, i wrote it myself. :)  My caching code is pretty basic.  I created an object that has a few dictionaries of users in roles, and roles for users, some helper methods, and store it in the global Cache object.

And as with any caching, we need to make sure that it expires so that "stale" data isn't stored.  By default, the cache expires absolutely after 12 hours, or whenever an IISRESET is executed.  The number of hours to stored cached Hybrid Roller calls is a configuration setting, so that you can tune this to your organization's frequency of changing group information.  Finally, ONLY AD calls are cached.  Since SQL calls are only a stored procedure execution and enough .NET code to invoke it, they will always be fast and simply not work caching.

That's it!  Like I said, with the Hybrid Roller, I have finally implemented all of the major facets of "Hybrid" membership, and brought them to SharePoint.  You can download the source code, or just the installer, here.  Again, to upgrade, run this installer, uninstall the Hybrid Provider from your web application(s) and then reinstall.

In addition to the new Hybrid Roller, I've made some updates around the rest of the Hybrid family as well.  The picture control for the Hybrid Profiler now uses an ASPX page and GDI to resize pictures so that they look better on the profile page.  Also, you have the option to not select an image for someone.

I also updated all the features' (Provider, Profiler, Roller, and AJAX) XML files to contain the following setting: ActivateOnDefault="FALSE" in their markup.  I noticed that after using my installer to setup the Hybrid Provider, subsequent new web applications would have it installed already.  And then, when debugging some application provisioning code, my feature receivers for the Hybrid Provider would be invoked!  Wow!

Well, I decided that this was not intuitive behavior, so I turned it off.  If you already have the Hybrid Provider installed (and don't want to mess with it), here's how to make this change manually so you don't have the Hybrid Provider hanging out on new web applications that you don't intent it to:

  1. Navigate to the SharePoint "Features" folder, which is by default "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\12\TEMPLATE\FEATURES."
  2. Open the "HybridProvider" folder.
  3. Open the "Feature.xml" file.
  4. Add "ActivateOnDefault="FALSE"" as an attribute to the main "Feature" element.
  5. Save the file.
  6. Repeat steps 2-5 for the "HybridProfiler" folder.
  7. Repeat steps 2-5 for the "HybridRoller" folder.
  8. Repeat steps 2-5 for the "AJAXInstaller" folder.

On a completely unrelated note, I made an addition to my Web Part Page Manager.  I added a "Page Clearer" web part that removes all web parts and MOSS Publishing field data from a page.  Also, I changed the web parts to be actual web parts, not user controls hosted as Smart Parts.  When I was testing the Page Clearer, I noticed that it took just as long to add a SmartPart, wire up my user control, and publish the page than it did to just manually delete all the web parts.

Other quick changes include renaming the "Zone Hider" to the "Zone Expander."  Now you just add the web part to a zone, and the width of that zone is set to 100%; all other zones are hidden.  Finally, when the Zone Expander is deleted, it re-shows all of the zones and resets the page.

And now there's a solution to install it.  This is actually my first crack at solution deployment; I usually wuss out and use features and batch files.  So to install the Web Part Page Manager, add and deploy the solution to your farm, making sure the intended destination site collection is specified in your STSADM call.  There's an "Install.bat" file in the project source code folder that you can run that will retract and deploy the solution for you.

That's all!  Have fun!

The Problem

Animation in Silverlight certainly gets us (“us” being .NET developers) further than we’d ever thought we’d go in terms of sexiness on our web sites. With a few lines of XAML, and a few more lines of C#, I can do what all those Flash hippies have been doing for years. Unfortunately, WPF and Silverlight animation is missing something, and of course, it’s the first thing I wanted to play with.

This missing something is animation along a curve. Now by “missing” I’m not saying that it’s not there. Oh, it’s there. The way to animate along a curve is to establish your key frames as SplineDoubleKeyFrames and specify a Bezier curve for each. Bezier curves blow; they are hard to work with (going all the way back to Paint for Windows 3.11, which had Bezier curves that were impossible to get to look how you intended) and conceptually aren’t animatably intuitive.

For Bezier curves, you define two control points that essentially bend a line. But when I’m visualizing such a scenario, I want a concrete factor to define my curve, like an angle or a radius, not an abstract “control point” that somehow defines a motion. In fact, I want to think of my curve as an arc, which is ultimately part of a circle.

Circles make sense to me. “Control Points” do not.

Anyways, want I wanted to do was, given a start point and an end point on a canvas, animate any UIElement between them – not using standard linear interpolation – but rather on a curve. The “size” of this curve would be defined by an angle. For example, if I wanted to go from the “top” of a circle to the “bottom” my angle would be 180 degrees. Or if I wanted to go around a quarter of the way, I’d have a right angle (90 degrees).

The Answer

The right way to do it is to use the Bezier curve stuff with Splines and all of that. Adam Nathan, in WPF Unleashed, (best WPF book I’ve read) suggested to use Blend to design the animation, and then port the generated XAML back into Visual Studio. But please! Defer to a design tool? Don’t get me wrong: Blend is awesome, but I’m a developer. If Microsoft wants to continue to drive a wedge between these roles, then that’s fine; don’t burden me with a dependency on Blend!

So I wrote my own animation from scratch. Allow me to introduce: CircleAnimation (up on CodePlex)! The idea behind CircleAnimation is to visualize a circle defined by two points and an angle, and animate along the arc between these points, carved out by the angle. This is all wrapped up in a user control that you can place inside your Silverlight control, wire up the UIElement to animate, and populate all the properties, which are:

  • Start Point – where the animation starts (Point)
  • End Point – where the animation ends (Point)
  • Arc Angle – the angle that defines the “size” of the arc (Double)
  • Is Start To End – true to go “forward;” false to go “backward” (Bool)
  • Is Flipped – see “Math” section (Bool)
  • Duration – the amount of time, in seconds, the animation takes (Double)
  • Target – the Id of a UIElement to animate (String)
  • StartAnimationOnLoad - weather or not to animate when the control loads (Bool)

I also expose the underlying Storyboard object, so consumers of this control can manipulate the more advanced aspects of the animation, such as accelerations, repeat behavior, etc.

The Designer

But how do we know what angle to use? Do we have to guess at the start and end points? Of course not! CircleAnimation comes with what I call a run-time designer. Okay, so it’s just a bunch of stuff that is drawn when the “ShowDesigner” property of the control is set to true. However, it alleviates the guesswork by allowing you to design your animation right inside your user control. The designer even creates a line of XAML that you can dump back into your control when you’re done.

Among this “bunch of stuff” that’s drawn are draggable start and end points, the center point, and a nice gradient arc (green-to-red shows start-to-end) that’ll define the animation path. It has a slider to define your angle, and options to show IsFlipped and IsStartToEnd, so you can tweak the motion. Here’s what it looks like:

clip_image002

The gray rectangle is what’s being animated. The green dot is the start point, and red dot (which is currently being dragged) is the end point. The three blank lines define a triangle formed by the start point, end point, and the center of our imagined circle. (These will come into play when I go over the math).

Other than dragging the start and end points and the lines, the box in the middle is your designer, containing all the controls you need to create an animation across any arc of any size in any direction; indeed, any curved animation you want! And as you change your curve, the textbox at the bottom of the box will update with the XAML needed to perform the animation.

So here’s the design experience, in a nutshell:

  1. Add a reference to the CircleAnimation assembly to your project.
  2. Add a namespace to your control, presumably called xmlns:CircleAnimation
  3. Have a canvas be the main parent control of your user control.
  4. (Note that only direct children of this canvas can be circle-animated.)
  5. Put the following “empty” circle animation control inside the canvas:

    <CircleAnimation:CircleAnimation x:Name=”ca” Canvas.ZIndex=”999” ShowDesigner=”True” Target=”[name of UIElement to animate]” />

    (The ZIndex is just to make sure that the designer is on top of everything else, otherwise it’ll be hard to drag and see what’s going on.)
  6. Run your app.
  7. Use the designer to create your animation arc.
  8. You can click on any UIElement that is a direct child of the canvas in step #3 and begin designing the animation for that object.
  9. Copy the contents of the XAML textbox.
  10. Close the app.
  11. Paste the XMAL back over your original CircleAnimation declaration. You might need to tweak some things, but now you’re done.  The generated XAML always sets ShowDesigner to false.
  12. Switch ShowDesigner back to true and repeat to change things around, or to create an additional CircleAnimation for another object.

That’s it! Have fun circularly animating!

PS - The Math

I know no one’s going to read this, but I just needed to prettily write it all out at least one time…if only to ensure that it made sense to me. This is the culmination of over 20 pages of scribbled algebra and trigonometry, not to mention answers to the high school math homework assignments I found on the web when searching for things like “Pythagorean Theorem” and “Distance Formula.”

Here’s a diagram that the rest of this discussion references:

CircleAnimationMath

Given the start point (P1), the end point (P2), and the angle that carves out the arc angle (Φ), we can determine a circle that contains both points, and based on the angle, the arc of the animation. The idea is to visualize an isosceles triangle formed by the line connecting these two points, and two radii (r) of our circle. The line connecting these points is the baseline (b) for the triangle. Therefore, the apex point of the triangle is also the center of the circle (C).

In order to calculate (C), the first step is to get the radius (r) of the circle. Using the Distance Formula from (P1) to (P2), we can get the length of (b). Once we know (b), then (b/2) is the short side of a right triangle whose other two sides are a radius (r) of the circle, and the hypotenuse (a) (the altitude of our isosceles triangle). Taking advantage of the ever-popular SOH-COA-TOA method, we can set:

(r) = (b) / [2 * sin (Φ/2)]

And solve for (r). (We get (Φ/2) since (a) is the altitude of the triangle, and thus its perpendicular bisector, slicing the apex angle in half.)

Knowing (b/2), and now (r), we use the Pythagorean Theorem to solve for (a). With the altitude (a) of the isosceles triangle known, we can now go after the apex point (C). We’ll need (M), which is the midpoint of (b), gotten via the Midpoint Formula. The following equation, taking in (M), (a), (P1), (P2), and (b), gives us the two possible values for the coordinates of the apex point (C), again, which is the center of the circle:

x4 = x3 – [(a) * (y2 – y1) / (b)]
y4 = y3 + [(a) * (x2 – x1) / (b)]

or

x4 = x3 + [(a) * (y2 –y1) /(b)]
y4 = y3 - [(a) * (x2 – x1) / (b)]

The reason we have two possible values is because the center of the triangle can point either “up” or “down.” This is due to solving the above equation (which I’ve simplified from a more complicated form) having involved taking the square root of both sides, which leaves us with a +/- in front of the result. The one you want depends on how you draw the angle, which is ultimately a parameter to the calculation.

When you work with ArcSegments in WPF / Silverlight, there is a concept of small arc vs. large arc. This is due to the fact that the input angle is generally between 0 and 180 degrees (if it’s greater than 180, you need to set an additional property, which is lame). In CircleAnimation, the angle can be between 0 and 360 (exclusive), so “large arc” and “small arc” don’t really apply; angles greater than 180 degrees will always be “large.”

Therefore, there’s just one property called “IsFlipped.” IsFlipped just means “go the other way.” The following example has IsFliped set to true (and an angle greater than 180 degrees):

clip_image006

And this one has it false:

clip_image008

But if this angle is less than 180, you’ll see this:

clip_image010

Verses this:

clip_image012

For the same values of IsFlipped as in the first two examples.

Back to math: now we can get (Z), or the zero point of the circle, by translating (C) down the x-axis the length of the radius.

x5 = x4 + (r)
y5 = y4

(Z) is important, since it defines the where arc angles start, i.e. where “zero degrees” is. With this orientation, for example, we now know that if (P1) was at (Z) and (P2) was at (x4, y4 + (r)), the arc we need to animate over is -90 degrees (since SilverLight goes from 0 to 360 degrees clockwise).

With (Z), we can now go after (Θ), which is our start angle. This is crucial, since we need to know the angle between (Z)’s radius and the radius between (C) and the first point of our animation. Then we “animate” over every angle from this point (P1) to the end point (P2), increasing (or decreasing, if we’re going end-to-start) this angle by one degree (or finer, for smoother animations).

To get (Θ), we first need (d), the distance between (P1) and (Z), gotten via the Distance Formula. With (d), we have another triangle, with its two other sides at (r). Knowing all three sides of the triangle, we can use the Law of Cosines to calculate (Θ). Here’s the equation:

(Θ) = arccos { [ (d)^2 – 2 * (r)^2 ] / [ -2 * (r)^2] } / π

(In all these trigonometric functions, keep in mind that .NET deals with radians, so I had to do some conversions. One degree is roughly equal to 0.0174532925 radians.)

Now that we know (Θ) and (r), we should be able to calculate what (P1) should be. (P1), the starting point of the animation, can now be represented by:

x1 = (r) * cos ((Θ)) + x4
y1 = (r) * sin ((Θ)) + y4

And then, any point along the animation for some n between (Θ) and (Φ) would be:

xn = (r) * cos ((Θ) + n) + x4
yn = (r) * sin ((Θ) + n) + y4

And so on, for (Φ) more degrees (until n equals (Φ)). In other words, if we iterated over (Θ) to (Φ), then the end point, (P2), would be:

x2 = (r) * cos (Φ) + x4
y2 = (r) * sin (Φ) + y4

We need to tack on the coordinates of the center point, (C), to respect the origin of the circle. Now there’s just one problem: what if, starting from (Z) and moving along the circle, we hit (P2) before (P1)? Well, it almost doesn’t matter. Instead of redrawing the figure above to work for both cases, I stumbled onto a short cut.

What I do, as soon as I have (Θ), is pass this to the equation above for (P1) – rounding to one decimal point. If the result of this isn’t (P1), then all I do to fix it is flip (P1) and (P2), and multiply (Θ) by -1! This basically makes everything work backwards, but conceptually is valid and equivalent. The only ramification is that, based on the different combinations of weather the angle is greater than 180 degrees or “IsFlipped” is true, change what “start-to-end” means.

In other words, if I flip my start and end points, then an animation going from start-to-end is really going from end-to-start. I know this sounds like a hack, but I don’t change anything in the UI; it’s all in the trig, and all ends up working out the same. This actually does it for the math, since we now have the means to calculate, in order, every coordinate on the circle that the object we are animating will need to hit.

All I do then is actually iterate from (Θ) to (Φ), or, more accurately, (Φ) times starting from (Θ). I build two collections of DoubleAnimationUsingKeyFrames (one for the Canvas.Top (y-coordinate) and one for the Canvas.Left (x-coordinate) property of the object to animate. The rest is straight WPF animation.

There are some interesting things going on, however. For example, based on the direction of travel and the size of the angle, sometimes I need to be subtracting degrees from (Θ) instead of adding. Also, for some reason, setting each key frame’s time to KeyTime.Uniform blew up. Therefore, based on the duration and the number of angles I am animating, I needed to create a time slice and increment the time span of each key frame by it.

That's it!  Again: get all of the source code up on CodePlex.  Have fun circularly animating!

If anyone lives in the Chicagoland area and really really really wants to haul it up to Grays Lake, IL, I'm going to be giving my first-ever keynote at the Lake County .NET Users Group!

My talk is going to be about the Hybrid Provider, and how it facilitates using SharePoint as an extranet. 

Here's a link to the LCNUG site with all the details and registration.  Come out if you can!

(And yes, there will be free food!)

Catalyst is hiring!  We have a lot of SharePoint projects closing, and my boss won't let me work over 90 hours a week...something about my diminishing productivity after 5 AM or smelling funny at status meetings after pulling an all-nighter.  Whatever.

Anyways, for serious, we are looking for SharePoint developers who have at least one year of experience working with WSS 3.0 / MOSS 2007 in any of the following areas:

  • SharePoint Development
    • Knowledge of the SharePoint APIs
    • Consumption of SharePoint web services
    • Feature Deployment
  • SharePoint Design
    • Experience with SharePoint Designer
    • Branding with Master Pages, Themes, Custom Layouts, and CSS
  • SharePoint Administration
    • Farm Configuration
    • SQL Optimization
    • Configuring Search
    • Site Provisioning

So if you live in the Chicagoland area, and would like to work for a fantastic, growing custom .NET software development firm in the loop, please send me your resume! 

Please visit Catalyst's website for more details about us.  Thanks!

I've added a new control to the Hybrid Profiler that supports pictures for SQL users.  What it does is gets the images from a Picture Library, and loads them into a drop down for selection when the profile page is in "Edit" mode.  There's also a thumbnail next to the drop down to make the user experience a little richer.

In addition, when you save changes to your profile, UserInfo.aspx will attempt to sync your properties with the WSS profile created for your SPUser object.  More specifically, it calls into the hidden WSS user information list, gets the list item for the current user, and attempts to update each WSS user profile property (i.e., each field in this list) with the the matching value from each property of the Hybrid Profiler.

Here's what that code looks like:

   1:  private void UpdateWSSPrfofile(string loginName)
   2:  {
   3:      //initialization
   4:      int userID = SPContext.Current.Web.CurrentUser.ID;
   5:   
   6:      //impersonation
   7:      SPSecurity.RunWithElevatedPrivileges(delegate()
   8:      {
   9:          //open site
  10:          using (SPSite site = new SPSite(SPContext.Current.Site.Url))
  11:          {
  12:              //open web
  13:              site.AllowUnsafeUpdates = true;
  14:              using (SPWeb web = site.RootWeb)
  15:              {
  16:                  //update wss profile
  17:                  web.AllowUnsafeUpdates = true;
  18:                  SPListItem userItem = web.SiteUserInfoList.GetItemById(userID);
  19:                  foreach (SettingsPropertyValue prop in ProfileHelpers.GetPropertyValuesForUser(loginName))
  20:                  {
  21:                      try
  22:                      {
  23:                          //attempt each property once at a time
  24:                          userItem[prop.Name] = prop.PropertyValue;
  25:                          userItem.Update();
  26:                      }
  27:                      catch { }
  28:                  }
  29:              }
  30:          }
  31:      });
  32:  }

Finally, I've added a new attribute to Hybrid Profiler properties: ServiceURL.  What this does is allow you to specify a string (for example, "~/_layouts/HybridProfiler/PictureControl.asmx") so that you can wire up an AJAX web service to your control.  This will enable asynchronous JSON calls into managed code, effectively giving your control the concept of "code behind" while still allowing it to dynamically integrate into the Hybrid Profiler infrastructure.

This is all part of a round of updates I've made to the Hybrid Provider 2.0.

The code has been uploaded to my CodePlex project site, under the latest release.  The easiest upgrade path is to download the latest installer, and use it to uninstall then reinstall the Hybrid Provider.  This will activate the new feature receiver code, which provisions a Picture Library to store the pictures, adds the proper controls to the UserInfo.aspx page, and updates all the configuration stuff automatically.

Have fun!

After seeing all of the amazing technologies at PDC that are coming out from Microsoft, I've decided to fly though all of the side projects on my plate so I can start playing!  This includes a round of updates I've been meaning to make to the Hybrid Provider 2.0.  Here are some of the highlights:

  • Automatic provisioning of the "HybridProvider_ResetPassword" stored procedure on installation.  Read more about that here.
  • Added a button on the installer that launches the aspnet_regsql database provisioning tool, so that EVERYTHING you need to install the Hybrid Provider from scratch is all in one place.
  • Catalyst.SharePoint.ActiveDirectory.  I've moved all AD code into one assembly, which should make referencing AD code much less messy.
  • Made better use of build events in Visual Studio.  Projects in the solution used to redirect their output to the "Installation Files" folder in the HybridProviderInstaller project.  I moved this redirection back to the respective bin folder, and added build events to copy the project output (DLLs, ASPXs, ASCXs, etc.) to the installer's folder.  This should make development and release builds seamless.
  • Miscellaneous bug fixes
    • Fixed the code in several places to remove hard-coded "http" strings so that enabling SSL on your SharePoint environment will work seamlessly with the Hybrid Provider.
    • Added a lot more error logging (to the event log).
    • Other random optimizations / minor bugs / housekeeping

Nothing very compelling, I know, but I figured it was worth a mention in case anyone who has downloaded the code ran into any issues that these updates might fix.  Also, a quick FYI: if AD users are getting in and SQL users are not, and there's no error reported anywhere, make sure that the "HybridProvider_ApplicationName" setting in your web.config file matches the name of your application in the SQL database (should be the only row in the "aspnet_applications" table - you'll see what I mean; it might have defaulted to "/" instead of the name of the web app).

The code has been uploaded to my CodePlex project site, under the latest release.  The easiest upgrade path is to download the latest installer, and use it to uninstall then reinstall the Hybrid Provider.

Have fun!

Okay so yesterday I got really excited about Microsoft Azure, especially when SharePoint was one of five highlighted technologies that would be natively hosted in the cloud.  These technologies are the "services" that make up, well, the could, and are Microsoft's bet that the current software development paradigm trend shift from SOA to services will really take off.

These services are:

  • .NET Services
  • SQL Services (renamed from SQL Data Services)
  • SharePoint Services
  • Live Services
  • Dynamics CRM Services

I haven't heard anything further about SharePoint Services, other than a use of it in a demo.  This demo started with someone in Dynamics CRM creating a new timesheet.  They then opened Word 2007, and had a plug in to their CRM cloud that allowed them to open a status report template, and populate it with data from the timesheet.  Finally, still from the Word document workspace, they uploaded the timesheet to a doc library in SharePoint Services.

The home page of this SharePoint site had a cool Silverlight control that read into these status report documents and showed an aggregator gauge-looking Silverlight control that summed up the total hours and amounts billed.

But they showed us nothing more.  There was, however, a breakout session that went over SharePoint Online, which is not part of Azure, but sits on top of it, along with services like Exchange Online, Dynamics CRM Online, etc.  These "Online" offerings are essentially just a watered down, hosted version of the environment. 

Which means a great option for SMB's, but boooooooring for developers.  In SharePoint online, we get no server-side code, no Forms Services for InfoPath, no access to IIS or the web.config, and no feature / solution deployment.  This only leaves master pages, out-of-the-box web parts, whatever SharePoint Designer can do, and, interestingly, Silverlight controls.

Now when it comes to SharePoint projects, you're going to need code in some way or another if the client wants even only moderate customizations or straight up custom functionality.  This means that SharePoint Online is simply not a solution right now for enterprise portals that have a heavily branded look & feel, or custom functionality.  By "right now" I mean that we were told more functionality was coming in future iterations of SharePoint Online; unfortunately, we live in "right now" and not V.next.  

So in my opinion, SharePoint Online is great for organizations that don't have or want the IT infrastructure to deploy SharePoint to their network.  If you need a portal in the cloud, you can get one.  The pricing model is released, and there are two basic flavors tantamount to WSS and MOSS functionality.  But if this portal is going to be anything more than a "SharePoint Blue" site collection with document libraries and announcement lists, I don't think SharePoint Online is the solution you're looking for.

Windows Azure was just announced, seriously, like fifty one minutes ago at PDC 2008 by Ray Ozzie at Microsoft.  It is a new operating system that implements the whole “cloud” environment for hosted services and applications.  The first few times I’ve heard or read about the cloud (in general) I had the same reaction I always do when I hear about new development trends or paradigm shifts.

That reaction is, best case, I’m going to have to un-learn then re-learn what I know.  Whatever I’ve finally mastered is now obsolete, and I need to start over.  That’s best case.  Worst case is, holy ***, Microsoft has written me out of a job, tantamount to the jobs we’ve written people out of by automating Excel, or coming up with a new WF workflow.

However, with Azure, that’s not the case.  Basically, there are now three tiers of environments:

·         End-users running Vista on their desktop computers.

·         IT Administrators running Windows Server on their on-premises network.

·         Developers deploying to Windows Azure in the cloud Microsoft data centers.  This new tier is being called the “Web Tier” – when before the highest level of enterprise was a large web farm completely within the organization’s infrastructure (i.e. the above bullet).

The reason this is not the case is because we can leverage our existing knowledge against the Microsoft .NET Framework, SQL Server, Exchange, SharePoint, CRM, and Live either against our on-premises servers or in the cloud.  We don’t have to re-learn anything new; the only additional step is a wizard to deploy our applications to Azure, and, of course, an XML configuration file.

I was very afraid that everything was going to be different, and technically easier and more abstracted, meaning of course more difficult to deal with when something goes wrong or needs to be extended outside the box.  But now we have a “Software + Services” concept from Microsoft, where companies can not only chose between a conventional on-premises server environment or a hosted “cloud” environment, but also elect to mix the two together!

A quick example of this is something being called “Federated Identity” where the Azure manages user authentication for us.  On your Windows Server 2008 domain controller running Active Directory, you can configure a connector to Azure, so that when users hit your web application from the Internet, they don’t get a credential challenge: Azure can talk to your AD, circumventing any firewalls or other network security that could cause developers headache, and get them in!

Referring again to those three bullets above, Windows Azure doesn’t sit on top of Windows Server or anything like that.  It’s really side-by-side.  In an ACT-ish analogy… Azure : cloud :: Server : computer.  Where Windows Server is an OS for a single computer, Azure is an OS the abstracts any number of virtualized computers in a data center.  At the end of the day, it’s the same experience for our users, and a very similar experience for developers. 

They even demoed an Azure SDK that added project template for a Cloud service in Visual Studio 2008.  This created a new solution with two projects: a standard ASP.NET web project, and a cloud service project.  The ASP.NET development experience was the same.  When you’re ready to deploy, you select “Publish” from the cloud project, go through a wizard, and your code is on the Internet, running on Azure!

That’s all for now; more to come as more “surprises” are unveiled to us!

Something that's been passed over in all of my Hybrid Provider work has been some of the cool little tweaks I've come up with to get some of the "supporting characters" to work.  If custom SharePoint authentication plays the lead role, then login pages, administrative web parts, and SQL user management tools might not be center stage, but they are certainly crucial.

I've uploaded a ZIP file called "Hybrid Provider - Power Tools" to the Hybrid Provider CodePlex project site.

So what I'd like to do is give a few descriptions of how these pieces work, and the hacks (er...customizations) I've had to do to get them this far.

  • Login Page
  • Admin Tools
    • Create User
    • Remove User
    • Forgot Password
    • Change Password
    • Reset Password
  • Active Directory Layer

Login Page - UserLogin.aspx

As you'll find with most of this functionality, these pages and controls are essentially wrappers around the standard ASP.NET 2.0 "user" user controls.  The login page's bread and butter is, believe it or not, a Login control!  (As I've said before, don't rename this to Login.aspx - your page's class will collide with the Login control's class, and ASP.NET will barf.)

All you have to do to hook up a custom provider to a custom login page is set the "MembershipProvider" property of the user control equal to the name of the class of the provider (for example, "HybridProvider").  Then via reflection magic, everything works.  (Note: this will be the case for all wrapped ASP.NET "user" user controls mentioned in this article, so I won't have to repeat myself every time.)

The interesting part of this user control is a harmless little "remember be" check box.  What this does, on the surface, when checked, is create a cookie on the user's machine that automatically logs them in until they explicitly log out or change to a different user.  However, it does a lot more than that behind the scenes.

"Client Integration" in SharePoint means that installed Office applications (Word, Excel, PowerPoint, Access, etc.) can be called upon to open content in SharePoint, and even modify it offline.  With Windows Auth, this isn't an issue.  However, with forms-based auth, browsers and client apps cannot communicate without the aforementioned cookie. 

Therefore, SQL users of the Hybrid Provider will need to check this box if they plan to look at a list in the datasheet view, open a Word document form a form library, or export a list to Excel.  And that sucks.  So we need to make it un-suck with some JavaScript that automatically checks and hides this box. 

This way, users don't have to perform any extra steps when logging in to get all of the Office integration intrinsic to a Windows user's client experience in SharePoint.  The only drawback, I guess, would be that if someone on that machine ever needed to get to the login page after the first time they enter the portal, they'd have to explicitly log out.  Not a bad tradeoff, since they can do this in SharePoint 2007 from any page.

Here's the markup for UserLogin.aspx.  Lines 9 and 14 perform the hiding and checking, respectively:

   1:  <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="UserLogin.aspx.cs" 
Inherits="Catalyst.SharePoint.Web.UserLogin" %>
   2:   
   3:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   4:   
   5:  <html xmlns="http://www.w3.org/1999/xhtml" >
   6:     <head id="hdHeader" runat="server">
   7:          <title>Hybrid Provider Login</title>    
   8:      </head>
   9:      <body onload="document.getElementById('logLogin_RememberMe').style.visibility = 'hidden';">
  10:          <form id="frmHybridProviderLogin" runat="server">
  11:              <table style="text-align: center; width: 100%;">
  12:                  <tr>
  13:                      <td>
  14:                          <asp:Login ID="logLogin" runat="server" MembershipProvider="HybridProvider" 
RememberMeSet="true" RememberMeText="" />
  15:                      </td>
  16:                  </tr>
  17:              </table>
  18:          </form>                 
  19:      </body>
  20:  </html>

Admin Tools - CreateUsers.ascx

All of these controls are intended to be hosted in SmartParts that are on an administrative web part page somewhere in your portal.  This page should be locked down, obviously, in case a status meeting goes bad for example, and someone runs back to their computer and deletes their manager's account.

The first control, CreateUsers.ascx, is actually used to create users.  Talk about self-documenting code!  In the spirit of the others, it wires up the Hybrid Provider as it's MembershipProvider to add a new account to SQL, respecting properties of the Hybrid Provider, such as password strength rules, requirements of question and answer, etc.

There are two little enhancements I've made to the base functionality of this control.  The first one is some simple code behind the wrapped ASP.NET CreateUserWizard's OnCreatedUser event.  This code adds the newly-created user to the root web's SiteUsers collection, so that when they log in, they are at least authorized on the home page and don't have to see any access denied error messages.  Here's that code:

   1:  protected void OnCreatedUser_Click(object sender, EventArgs e)
   2:  {
   3:      using (SPSite site = new SPSite("http://server"))
   4:      {
   5:          site.AllowUnsafeUpates = true;
   6:          using (SPWeb web = site.RootWeb)
   7:          {
   8:              web.AllowUnsafeUpates = true;
   9:              web.SiteUsers.Add(string.Format("hybridprovider:{0}", this.cuwUser.UserName), 
this.cuwUser.Email, this.cuwUser.UserName,
string.Format("Added via Create Users on: {0}.", DateTime.Now));
   10:              web.Update();
   11:          }
   12:      }
   13:  }

On line 9, this.cuwUser is the name of the CreateUserWizard control.

The other enhancement is a trick to get around an annoying aspect of this control.  By default, after a new account is successfully created, the control hides all its fields and shows a continue button.  Clicking on this button, conveniently, does nothing.  There is a property on the CreateUserWizard control called ContinueButtonDestinationPageURL that, when set, will redirect to that page when the button is clicked.

But what if it needs to be dynamic (especially in SharePoint, when we can't anticipate what our URL will be at design time)?  So to deal with this dynamically, I handle another event in code: OnContinueButtonClick.  The event handler is one cute line of code:

   1:  protected void OnContinueButtonClick_Click(object sender, EventArgs e)
   2:  {
   3:      //redirect to current page
   4:      this.Response.Redirect(this.Request.Url.AbsolutePath);
   5:  }

Why a redirect?  Because this will guarantee two things:

  • The control will reset itself.
  • Any other admin controls on this page that have a list of SQL users will have their page load events fire, thus this new account will immediately be usable in them.

Admin Tools - RemoveUser.ascx

Ashes to ashes...dust to dust: the admin giveth; the admin shalt deleteth your account!  This control literally does the opposite of CreateUsers: deletes the user from SharePoint, deletes the account from SQL, then refreshes the page.

There used to be a separate control that removes users from groups, (since there's no single screen that audits a user's group membership in SharePoint) but in the spirit of deletion, I decided to combine it with this one.  So when you pick a user, you have the option to remove it from any SharePoint group they're in, or delete the account altogether.

The way it works is that there are two drop downs: one with SQL users and one with AD.  Based on the selection of a radio button with an option for both, only one of these drop downs will ever be visible at a time.  The main difference is that if an AD user is selected (a "Consultant" in the code), the button to delete the user will be disabled, since we don't want to be removing objects from Active Directory!  SQL users, ("Clients") however, can be deleted.

But in both situations, a check box list will be built containing all of the SharePoint groups the selected user is a member of.  Check off the ones to remove from and click the button and they are out!  This list can be filtered by a semicolon-delimited list of strings stored as an AppSetting in the web.config file with a key of "GroupNamesToIgnore."  I parse this string, and the name of any group the user is in that matches any substring of "GroupNamesToIgnore" will not be in the check box list.

This is useful, again, to prevent administrators from accidentally de-commissioning themselves, and since there are a few internal group memberships I didn't want to mess with.

They call to Membership.DeleteUser takes in the account same of the selected SQL user, and a boolean telling it to delete all user data.  This way, we don't have to worry about cleaning up any profile or role data in SQL; everything is blown away when this user's account is deleted.

Finally, as a side note, there is a lot of code in this control that is specific to the portal I first created the Hybrid Provider for.  If you are going to use this in your SharePoint environment, it might be easier to rebuild it from scratch, using mine as a reference.

Admin Tools - ForgotPassword.ascx

I was amazed to learn that, despite how pervasive computers have become in our world, people still forget their passwords all the time ! Unless they are typing them in every day or have a Post-It on their monitor with passwords written on it, it will quickly be garbage-collection from their brains.  And since people (more specifically, SQL users) don't necessary hit a portal every day, you had better have a mechanism in place for quickly helping users recover, change (next section), or reset (next next section) their password!

ForgotPassword.ascx is the "first line" of defense against forgotten passwords here.  This control is wired up to the HybridProvider, and will Email a user their password.  Since people who would need this have forgotten their password, it has to sit on the login page, or else they won't be able to get to it.

The way it works is that a user name is typed into a box, and a button is clicked.  It'll display a message if the user could not be found or if the password could not be retrieved.  The later occurs for two different reasons: the custom provider is not configured to allow password retrieval (read my posting about that here) or there was an error (I covered a probable cause of such an error here).

Assuming your Hybrid Provider and SharePoint Email are all set up, that's about it.  However, I did make one cool customization of the wrapped PasswordRecovery ASP.NET "user" user control.  It's actually not even really a customization, just an implementation of available functionality.

This control allows you to wire up a text document that contains the body of the Email that will be sent to the user.  Here's how to set this up in the markup:

   1:  <asp:PasswordRecovery ID="prForgotPassword" runat="server" MembershipProvider="HybridProvider" 
UserNameInstructionText="" SubmitButtonText="Get Password"
SubmitButtonType="Button" MailDefinition-Subject="Forgot Password"
MailDefinition-BodyFileName="/ForgotPasswordBody.txt">
   2:  ...
   3:  </asp:PasswordRecovery>

The MailDefinition-BodyFileName property, set to "/ForgotPasswordBody.txt," will read in a text file with this name that is sitting in the same directory as the user control.  Additionally, you can specify certain substitutions in this file to insert user-specific information.  ASP.NET has two right now:

  • <% UserName %>
  • <% Password %>

Placing either of these tokens on your text file will cause the ForgotPassword control to replace them with the actual user name and/or password of the user requesting their password.

Admin Tools - ChangePassword.ascx

Change password, which embodies the ASP.NET ChangePassword "user" user control, is actually the simplest one of the bunch.  It allows a user, once logged in, to change their password to someone else.  All of the customizations in this control I've already mentioned: wiring up the HybridProvider, and redirecting back to itself on a successful change.

However, one quick additional customization to mention is that there's a cancel button I saw fit to hide.  In order to get it looking correctly in SharePoint, I had to set two properties in the ChangePassword control's markup to a value of "0px" - these being CancelButtonStyle-Width and CancelButtonStyle-Height. 

Admin Tools - ResetPassword.ascx

This brings us to ResetPassword.ascx - the last line of defense against forgotten passwords, and indeed the most complicated.  This is an administrative tool that builds a drop down of SQL users, and allows an admin to type in a password for the selected user, update it, and optionally Email to new password to the user.

Hopefully, you're thinking one of two things.  Either: "Wow!  That's impossible!  How do you deal with the password encryption?  You're a really great developer / person!"  Or: "You fucking hacked it, didn't you, jerkface?"

I did hack it - but not really.  It's not a hack because I use the SQL membership provider functionality (and one custom stored proc) and therefore don't have to mess with the SALT, the machine key, or any of that.  Believe me, before I came up with this tactic, I was tempted to try the encryption myself...glad I didn't!

Now of course, I tried using the out-of-the-box methods (in the Membership static class) first.  These however generally blow up depending on your password format and other provider settings.  Sometimes, if you are using clear text for example, they'll work, but you won't have all the necessary information.  You need the current password of the user to pass to the "ChangePassword" method, and you need their secret question's answer to pass to the "GetPassword" method to get the current password - and that all is, as previously stated - if these puppies work in the first place.

So I did it myself.  I stumbled onto this hack when trying to update user's passwords directly in the database (an awful eventuality, I know,  and therefore the reason these tools were created in the first place).  I noticed that I could update the encrypted text of a known password (and its SALT) from an old user and a new user's row in the aspnet_membership table of the ASP.NET user database, and then successfully log in with those credentials.

So the logic of this control is as follows:

  1. Create a temporary user with the password typed by the admin in the textbox.
  2. Pass the name of this temp user (which is <new guid>@<new guid>.com), along with the name of the user whose password we are updating (from the drop down of SQL users) to a proc. 
  3. This proc will select the password and SALT from the temp user, and set it thusly for the updating one.  It will also make sure the user has the correct password format, and reset any failed login attempts.
  4. Delete the temporary user. 
  5. [Optional] Email the user their new password if the corresponding check box to this option is selected.

The Email portion is interesting.  I want the Email sent to the user to be the same as the one they would have gotten from the login page had they used ForgotPassword.ascx to retrieve their password.  Therefore, I need to programmatically do what the ASP.NET PasswordRecovery "user" user control does.  This includes implementing the aforementioned replacements.  Here's that snippet:

   1:  //create a mail definition
   2:  MailDefinition md = new MailDefinition();
   3:  md.BodyFileName = "/ForgotPasswordBody.txt";
   4:  md.Subject = "SharePoint - Password Changed";
   5:  md.IsBodyHtml = true;
   6:  md.From = "admin@portal.com";
   7:   
   8:  //replacements
   9:  Dictionary<string, string> replacements = new Dictionary<string,string>();
  10:  replacements.Add("<% Password %>", this.txtPassword.Text);
  11:  replacements.Add("<% UserName %>", this.ddlUsers.SelectedItem.Text);
  12:   
  13:  //send email
  14:  new SmtpClient().Send(md.CreateMailMessage(this.ddlUsers.SelectedValue, replacements, this));

Lines 8 - 11 implement the replacements.  I said previously that these were the only two supported by PasswordRecovery.ascx.  However, I wonder (have not tried it) if, in code, we can specify as many replacements as we want.  That would be a cool experiment.  But I don't need it, so I leave it to you.

Line 1 requires a reference to System.Web.UI.WebControls; 14 needs System.Net.Mail.  Of course, for this technique and for PasswordRecovery.ascx to work, you'll need to make sure your web.config is properly set up to transmit Email.  SharePoint's Email doesn't necessarily need to be configured to make this happen; we're going straight to ASP.NET.  Here's what the web.config section should look like:

   1:  <system.net> 
   2:    <mailSettings>
   3:      <smtp deliveryMethod="Network" from="admin@portal.com">
   4:        <network host="mail.portal.com" />
   5:      </smtp>
   6:    </mailSettings>
   7:  </system.net>

You can also specify the port, credentials, etc. to customize the requires for your network.  This is the bare bones configuration.

That's it for the user controls!  Time to get all LDAP-y.

Active Directory Layer - Catalyst.SharePoint.ActiveDirectory.dll

The final supporting character in the Hybrid Provider's cast is the interaction between all of these sub systems and the actual system of record for user data - Active Directory.  AD always seems like a hot topic, since System.DirectoryServices is always an adventure to code against. I just get the impression that programming against it is edgy and risque. 

This namespace is such a light wrapper around all the underlying COM it abstracts, that it's translucent; you can actually see all the evil COM right though the DLL's skin.  Whenever you find yourself Googleing hexadecimal error messages, you know you're digging deeply into AD code. 

This makes the experience less than exhilarating, but the end result is pretty cool.  Again: there's just something about coding against AD - it makes me feel a bit godlike, whereby changing someone in my company's last login date gives me some type of power over his or her soul.  

Anyways, I've moved all AD code from around the Hybrid Provider into its own assembly.  If you look at the code, you'll see that it's organized as a series of more and more granular helper methods.  For example, the static class ADHelpers makes extensive calls into itself to do common tasks, such as get a DirectoryEntry object (the entry point into an AD forest) or get a property from a search result, etc.

Again, the best way to learn this is to look at the code.  However, I'll call out a few things here to get you started:

  • GetDirectoryEntry returns a DirectoryEntry object specific to the path you give it.  If you give it the "root" (something like LDAP://<domain controller name>), you'll get everything.  But it you want a certain object, like a user, you can give it the path directly to the object.  There are overloads that try to read the required credentials from the application's configuration file; others take it in in case there is no config file available.
  • LDAP paths are case sensitive.  Always use "LDAP://" to start (in upper case).
  • GetPropertyFromOtherProperty is a quick way to query for one property of a user based on another.  A common example of this is if you have a SID of a user, and need their Email address.
  • ADUser is a small class that allows you to "personify" an active directory user as the sum of the properties available in your farm.  They have strongly-typed properties for the "big ones" (Id, UserName, etc.) and then a dictionary to hold all properties.  This class allows us to load up an AD user, then manipulate it "off line" (ie - NOT directly in AD code). 
  • SetProperties can be used to save an ADUser back to AD.

If I were a SharePoint psychiatrist, (which indeed I may very well be...) I would have a list of panaceas tantamount to what a therapist might actually tell a real patient.  For example:

To a patient:

  • "You have a lot to live for!"
  • "She wasn't the right girl..."
  • "Yes, I can prescribe something for that rash..."

To a SharePoint developer:

  • "Remember, it's just an ASP.NET 2.0 web app hosted in IIS!  It's just HTML!"
  • "That's only supported in MOSS..."
  • "Yes, I can tell you what 'unknown error' means..."

The reason SharePoint is sometimes seen as a hostile environment to work with is because a lot of things just don't work, or perhaps more accurately, don't work the way you'd expect them to.  But people quickly forget that the nature of this beast is two-sided; the things that cause certain functionality to not work are in turn making many other things trivial to implement.

It's a trade-off, like everything is. 

But instead, people try things the one way they know how, then *** when that doesn't work.  So this article is going to be about a bit of an attitude adjustment, and doing what it takes to do something right.  JavaScript, AJAX, and SharePoint are just the supporting characters here.

First, the problem: I redid our company's time sheet system as a SharePoint solution.  The front-end is an AJAX-y animating user control hosted as a SmartPart.  The back end is all Linq to SQL.  The workflow is handled by WF.  Everything lives in SharePoint, and future releases will have Quick Books integration.

Now I've written a lot about SharePoint and AJAX, so this was nothing new for me.  But after our first round of testing, there was a bug about a draggable div not dragging correctly.  I was able to drill down into this problem and find that it only occurred when the page was scrolled.  My mouse event handler code was supposed to account for this.  Here's what that JavaScript looks like:

   1:  document.onmousemove = CaptureMouse;
   2:  ...
   3:  function CaptureMouse()
   4:  {
   5:      //initialization
   6:      var div = $get('divId');
   7:      ...        
   8:      //set drag location
   9:      div.style.top = Number(event.clientY) + Number(document.documentElement.scrollTop);
  10:      div.style.left = Number(event.clientX) + Number(document.documentElement.scrollLeft); 
  11:      ...    
  12:  }

Notice the use of document.documentElmenet in lines 9 and 10.  This is what you use to get the number of pixels the page is scrolled in either direction in IE7.  Since this is an internal app, I know that all my users will be using IE7, so no cross-browser consideration was necessary.

However, it didn't work; both values just returned 0.  I tried my user control on a normal page (not SharePoint) and it behaved just fine.  "Oh well," I thought.  "It's an internal app; they can just scroll to work around it."  Yep: I was about to write this off as something that just didn't quite work in SharePoint.

But that just didn't sit right with me.  There has to be a reason why!

So after frolicking around the server for a while, I stumbled onto the default master page (default.master in C:\Program Files\Common Files\microsoft shared\Web Server Extensions\12\TEMPLATE\GLOBAL).  Always intrigued by SharePoint-inized HTML, I opened it up.  The first thing I noticed is what led me to the answer: there was no DocType at the top of the file!  I immediately opened up a page in the portal (thinking that the DocType line might be generated on-the-fly or something), did a "View Source" and indeed, no DocType!

So I went back to the master page, and copy-and-pasted in a DocType from whatever Visual Studio generates for a new ASPX page.  Next I tried the page again, and guess what?  The draggable div now worked in SharePoint when the page was scrolled!  However, using a DocType (I tried several different versions and DTD's) CRUSHED the formatting of the page.  The colors, gradients, and images were all screwy in IE7.

So what does this mean?  Well that SharePoint's HTML is not really confirming to any DocType standards.  To me, this seemed very IE6-ish, so, referring to the code above, I switched my "document.documentElement" calls to use to older "document.body" that works in IE6.  And guess what?  That did the trick for SharePoint in IE7!

Now this wasn't the case for all the JavaScript; some confirmed to the browser version as advertised.  But my point here isn't to get into all of this technical detail.  All I want to say is that SharePoint is a robust application platform, but it's also a unique one.  And like all environments, there is supported functionality that works trivially, and there's fringe functionality, however pervasive in standard environments, (like straight ASP.NET) needs some effort to get working.

Have fun!

Have you ever tried to use custom layouts to programmatically provision web part pages via the SharePoint API?  F that!  It seems to me like you need to take a dependency on MOSS and the Office Server publishing infrastructure.  All this just to modify a web part zone?  There has to be an easier way to get tasks like these done!

Well there are a few possibilities, but nothing that pans out to be feasible.  The first thing I tried was the "right way" by which you need to install MOSS, activate all kinds of features that have strict - but yet not enforced - dependencies, and then finally write code that is going to be riddled with hard-coded Guids, content types, and other sources of ulcers.

So like I said, thanks, but F - off.  By "not enforced" I mean that if you try to activate these publishing features manually, and forget one or do them in the wrong order, SharePoint will blow up all other place, for example, when creating sub sites or pages of certain types.

The next approach was to keep things WSS-y.  So I created a document library in my site, and then created a web part page in that doc lib.  The problem was that (and I hate this) I couldn't figure out which parts of the API represent these UI's.  I started wandering down the path of inspecting the content type of the doc lib, then crafting a list item to match it, but I didn't see how to "use" an ASPX page as a list item. 

My head started to hurt, so I backed off.  Besides, I prefer to put my web parts on the home pages (Default.aspx) so that I get all the default navigation functionality.  In fact, as I realized at this point on my journey, what I really wanted to do was take the default layout on a site's home page, and crush the right zone. 

Of course, as another option here, you can do whatever you want with SharePoint Designer, but I want to automate the deployment of my web parts via feature activations.  Besides, I feel that Designer is best used only after 5:45 AM when you have a presentation at 9:00 that morning and absolutely nothing else is working.

The answer, or at least the correct path to the answer, came to me last night at AliveOne.  I was talking to my friend Jenna, fixated more on her gorgeous hair than her story.  "Inside out!"  It suddenly hit me.  "Don't attack it from the page level, but rather from the web parts within."  I needed to write some code in a web part that sacrificed itself by going up to its web part zone and kamikazeing it!  I then ran off from Jenna, got a pen from the bar, and scribbled the pseudo code used in this blog on the back of a DJ flyer.

Why I'm thinking about SharePoint in bars, unfortunately, is beyond me.

So how do you programmatically manipulate a web part page in SharePoint from a web part itself?  Well with the SPLimitedWebPartManager, and it's fully-loaded cousin, the SPWebPartManager.  All you need to know is a URL to get a reference to that page's SPLimitedWebPartManager.  Once instantiated, you have methods to add, remove, and modify web parts to or from the zones of the page. 

Check out a blog entry of mine for some sample code of this.  Almost all of my web parts nowadays are SmartParts, so the above is a good prerequisite article to this one.

One of the "limited" features (as the name implies) is that you do not have access to the Zones collection from this object, so the operations are pretty much limited to web part management.  However, if you have a full blown manager, you can do a lot more.  The tricky part is getting a reference to one of these puppies.

The only way I've been able to do this is to enumerate up the control tree until I find one.  So if I have a user control living in a SmartPart, then, interestingly enough, this.Parent is the SmartPart, and this.Parent.Parent is an SPWebPartManager.  With this, it appears as though we have programmatic access to the web part zone as if it were a WebControl!

Unfortunately, beyond changing default fonts and other styles, it didn't prove to be useful.  The following code did not hide the zone like I hoped (executed during page load of my user control):

   1:  //get the web part zone id -> reflection is used because I was having massive problems casting 
   2:  //among SmartParts and AJAXSmartParts. 
   3:  //For some reason, the types were not matching, probably due to XML type serialization
   4:  string zoneID = ((System.Web.UI.WebControls.WebParts.WebPartZoneBase)
this.Parent.GetType().GetProperty("Zone").GetValue(this.Parent, null)).ID;
   5:   
   6:  //get the manager
   7:  Microsoft.SharePoint.WebPartPages.SPWebPartManager mgr = 
this.Parent.Parent as Microsoft.SharePoint.WebPartPages.SPWebPartManager;
   8:   
   9:  //get the zone
  10:  System.Web.UI.WebControls.WebParts.WebPartZoneBase zone = mgr.Zones[zoneID];
  11:   
  12:  //DOESN'T WORK!
  13:  zone.Style.Add("display", "none");
  14:   
  15:  //DOESN'T WORK!
  16:  zone.Width = new System.Web.UI.WebControls.Unit(0, System.Web.UI.WebControls.UnitType.Pixel);
  17:  zone.Height = new System.Web.UI.WebControls.Unit(0, System.Web.UI.WebControls.UnitType.Pixel);
  18:   
  19:  //DOESN'T WORK!
  20:  zone.Visible = false;
  21:   
  22:  //but don't worry, we can change the backgound color...ugh...
  23:  zone.BackColor = System.Drawing.Color.BurlyWood;

I spent a lot of time hacking around here; I was sure this was the way to go!  Why expose a web control to us, but not allow any of the rendering attribution work?  Who knows.  So I next launched a page in my dev portal, and did a "View Source."  This is always the first step of a hack, I know, but I was very depressed that the "right way" left us so close to the solution.

I finally found that the web part zone was really just a cell in a table.  The default.aspx ghosted page has such a table with one row with three cells; the first has a hard-coded width of 70%, the last 30%, and the middle just a &nbsp; to keep the other two prettily in line.  I decided that it was time to hack.

And that meant JavaScript.  All I had to do was get my little hands on that row.  I would then give the first cell a width of 100%, and hide the rest.  Now this is going to be some ugly-ass JavaSc