My career in technology

This is part two of my exploration of a solution I recently completed, using an event handler to programmatically create and configure a site based on form data provided by users and a site template created and managed by a SharePoint power user.

We left off with the code needing to decide if it actually needs to do anything…


(Links to the other parts in the series are at the bottom of the post!)

        public override void ItemUpdated(SPItemEventProperties EventProperties)
        {
            SetInitialClassLevelVariables(EventProperties);
            try
            {
                //If it is passes all checks, run applicable commands
                if (CheckRequirements())
                {
                    DoAllSiteCreationSteps();
                }

                if (NeedToUpdateSubsites)
                {
                    DoUpdateItemInSubsiteLists();
                    DoChangeFactorFilters();
                }
                if (NeedToModifyExistingSite)
                {
                    //Run a few commands to prepare it for new data
                    //ToDo#1 set indicators back to not completed.
                    //Requirements and errorhandling for this not defined so this is not implemented.
                    //DoModifyExistingSite();

                    //ADD WEB SITE ADDRESS TO THE FORM
                    DoUpdateFormWithWebsiteAddress();
                }
            }
            catch
            {
            }
            finally
            {
                //Regardless, run the basic ItemUpdated commands
                base.ItemUpdated(properties);
            }
        }

In order to validate that the code actually needs to do anything, the code will now CheckRequirements().

(Naming your methods clearly after what it does makes for good human-readable code.)

If it returns “true”, then it needs to DoAllSiteCreationSteps(). If it returns “false”, there still may be some things to do.

        private bool CheckRequirements()
        {
            NeedToUpdateSubsites = false;
            NeedToModifyExistingSite = false;

First, we need to add two more class-level variables, so we can pass more that the true/false for the main check function.

namespace MyEventHandlerName
{
    public class ItemUpdatedEventHandler : SPItemEventReceiver
    {
        //Set up Class-scoped variables
        string serverURL;
        SPItemEventProperties properties;
        bool NeedToUpdateSubsites;
        bool NeedToModifyExistingSite;

We’ll get to code that change these values in a bit, but until we NeedToUpdateSubsites or NeedToModifyExistingSite, we want those to return “false” too.

            if (properties.ListTitle != "ProjectRequestListName" | properties.ListItem == null)
            {
                //We are only implementing this set of commands on the Project Request List
                //so if it is running on any other list, skip it.

                // If this event is being fired from a list other than the Project Request List,
                // only do base.ItemUpdated commands
                return false;
            }

Fairly self-explanatory, with verbose code comments. The code in this event handler is pretty specific to the ProjectRequestListName List, and until code is added to handle other lists, the event handler code should be ignored if the event handler does happen to fire from another custom list. Using the class-level variable properties, the code checks to see of the event warrants further attention.

If it does, it goes to the next check, if not CheckRequirements() returns “false”.

            if (properties.ListItem.ModerationInformation.Status != SPModerationStatusType.Approved)
            {
                //Only run the commands if the request is in Approved status

                // If the Approval status isn't approved, the item isn't finalized
                // only do base.ItemUpdated commands
                return false;
            }

Not only does this have to come from the correct list, but the item that has been updated needs to be in the Approved state. If it is not approved, the data may not be final yet, and the remainder of the code should not be run.

            if (properties.ListItem["Project Authorization"].ToString() != "Approved")
            {
                //Only run commands if the Project Authorization is in Approved status too

                // if the Project Authorization isn't approved,
                // only do base.ItemUpdated commands
                return false;
            }

The request form is for approval to move ahead with a project. If the data is finalized (SPModerationStatus is set to approved) but the request has been denied, the remainder of the custom code should not be run.

            if (properties.ListItem["Project Type"].ToString() == "ProjectTypeDoesNotNeedASite1" | properties.ListItem["Project Type"].ToString() == "ProjectTypeDoesNotNeedASite2")
            {
                //Only run commands if the project type needs a site

                // if the project type does not need a site,
                // only do base.ItemUpdated commands
                return false;
            }

Some project types don’t need a document collaboration site. If they don’t need a site, the code to make one is not run and the method exits.

            //We've gotten this far, its time to set the variables
            SetSiteVariables();

If the request is for a project that uses a collaboration site, then we begin to do some work. We know we’re in the right list and that the data is approved, so now we can access the data from the SPLitsItem to populate some class-level variables related to the site for the project.

        private void SetSiteVariables()
        {
            siteName = properties.ListItem["Title"].ToString();
            LOB = properties.ListItem["Line of Business"].ToString();
            parentSiteUrl = serverURL + "/MainProjectSite/ProjectSubsiteContainer/" + LOB + "/";
            siteTemplate = "SiteTemplateFileName.stp";
            siteUrl = "/MainProjectSite/ProjectSubsiteContainer/" + LOB + "/" + siteName;
            siteDescription = "Please Add A Description";
            BuildSite = new SPSite(parentSiteUrl + siteName);
            BuildWeb = BuildSite.OpenWeb();
            LOBSite = new SPSite(parentSiteUrl);
            LOBWeb = BusinessAreaSite.OpenWeb();
            ContainerSite = new SPSite(serverURL + "/MainProjectSite/ProjectSubsiteContainer");
            ContainerWeb = PropSpaceSite.OpenWeb();
            ProjectRequestWeb = properties.OpenWeb();
        }

Of course, this means I need to add the variable declarations to the code…

namespace MyEventHandlerName
{
    public class ItemUpdatedEventHandler : SPItemEventReceiver
    {
        //Set up Class-scoped variables
        string parentSiteUrl;
        string siteName;
        string siteDescription;
        string siteUrl;
        string siteTemplate;
        string LOB;
        string serverURL;
        SPItemEventProperties properties;
        SPSite BuildSite;
        SPWeb BuildWeb;
        SPSite LOBSite;
        SPWeb LOBWeb;
        SPSite ContainerSite;
        SPWeb ContainerWeb;
        SPWeb ProjectRequestWeb;
        bool NeedToUpdateSubsites;
        bool NeedToModifyExistingSite;

But look back at SetSiteVariables(). I’ve initialized several SPSites and SPWebs. While portions of these objects are managed code, the majority of these are unmanaged, and need to be disposed of properly when I’m done, regardless of anything else that happens before the end, or else run the risk of memory leaks (not good, in case you were still left wondering). So I added:

            finally
            {
                if (BuildSite == null) BuildSite.Dispose();
                if (BuildWeb == null) BuildWeb.Dispose();
                if (LOBSite == null) LOBSite.Dispose();
                if (LOBWeb == null) LOBWeb.Dispose();
                if (ContainerSite == null) ContainerSite.Dispose();
                if (ContainerWeb == null) ContainerWeb.Dispose();
                if (ProjectRequestWeb == null) ProjectRequestWeb.Dispose();
                //Regardless, run the basic ItemUpdated commands
                base.ItemUpdated(properties);
            }
        }

Theoretically, in most instances calling the Dispose() method of the SPSite should invoke the Dispose() method of the SPWeb as well, but not always. So we check if invoking the method is needed before invoking, but we invoke it if needed.

So, back to CheckRequirements():

            if (properties.ListItem["Web Site Address"] != null)
            {
                //If no site has already been created for this Project Request List Item,
                //the Web Site Address field will be null

                // if a site is listed in the project request, and the project has been changed (ItemUpdated fires),
                // and the changes have been approved (CheckApproval passes), then we get here.
                // We need to update the Lists in the subsites to reflect any changes
                NeedToUpdateSubsites = true;
                // but then we are done, do base.ItemUpdated commands
                return false;
            }

One of the fields in the list is the URL for the project site. On the request list UI this presents a clickable link to the site. The existence of data in the field also validates that the site has already been created.

If a site has already been created, this means the request form has been updated with new information after the creation of the site, and information on the project site and several other places may need to be updated as well.

We will NeedToUpdateSubsites later.

Of course, if the site hasn’t been created, the handler needs to move forward toward site creation.

            //To get to this part of the function, the form is in the right library, it is the
            //right type to have a site, is both authorized AND approved, and the site field
            //(properties.ListItem["Web Site Address"]) is empty.
            if (siteExists() == true)
            {
                //Manual check to see if there is a site at that name already to prevent appuke
                //Only do all the stuff if there isn't already a a site

                //If siteExists() == true, then there already is a site, but the site field is empty.
                //This should only happen when a follow-on project has been created that relates to
                //an existing (now "closed") project.
                NeedToModifyExistingSite = true;
                return false;
            }

This segment exists because even though I know that things can’t be made idiot-proof (because idiots are just too damned creative), I wanted to make absolutely sure that the event handler didn’t “bit-barf” in the event that there was a site already sitting where the new site was supposed to go. There are use cases where a second project was committed to after a first was completed, and the document site would be reused. There was also a possible use case in which someone made an error and assigned a name to a project that was the name of an existing site. In that case we wouldn’t want the event handler to overwrite the existing data in the site. This is the suspenders to the properties.ListItem[“Web Site Address”] belt.

If a site exists, we might NeedToModifyExistingSite.

The really interesting thing in this section is if (siteExists() == true). Here is what siteExists() does:

        private bool siteExists()
        {
            try
            {
                using (SPWeb WebToAdd = BuildSite.OpenWeb(siteUrl, true))
                {
                    if (WebToAdd.Exists)
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            catch
            {
                return false;
            }
        }

By using the SPSite.OpenWeb (String, Boolean) overload with the boolean set to true, OpenWeb() is forced to open only the web at the exact URL provided, instead of the default behavior, which provides the first SPWeb that exists as it moves up the hierarchy in the URL provided.

            //Hey look, we need to create a site!
            return true;
        }

And now we get to the heart of the code… next time!

I’d love your feedback. Is there something you think I could be doing better? Are there questions about the code I haven’t answered? Am I full of it? Let me know! Feel free to use these code samples in accordance with my usage policy.

Check out Part One here (The Setup).
You are reading Part Two.
Check out Part Three here (If You Built It…).
Check out Part Four here (…They Will Come).
Check out Part Five here (Bring ‘Em All In).
Check out Part Six here (Ride the CAML!).
Check out Part Seven here (Pointing The Way).
Check out Part Eight here (Taking Part).
Check out Part Nine here (Deconfigured).
Part Ten (The Log Blog) is coming!

More posts about SharePoint.

Advertisements

Comments on: "Automated SharePoint Site Provisioning Solution – Act Two (The Decision)" (2)

  1. […] Automated SharePoint Site Provisioning Solution – Act Two (The Decision) […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: