Skip to main content
 
Go Search
Home
Categories
Bloggers
By: Daniel Herzog | Posted: April 2, 2010 at 9:39 AM
 

I've used methods in the past for creating printer-friendly versions of SharePoint pages, with varying degrees of success.  But what I wanted was a simple, flexible solution that could easily be applied to existing sites.  I found one here:  http://www.iotap.com/Downloads/SharepointWebParts.aspx (scroll to the bottom).

 

Now this solution isn't going to work for every situation you'll encounter, but for several of my publishing sites it fit the bill nicely.  Basically the solution as it comes will dynamically produce a new page consisting of the content inside the master page main placeholder - essentially the main content of your page, without the navigation, branding, etc. that can really clutter a printout.

 

It does this with a custom script that grabs a handle on a DIV, and then dynamically builds a new page based on its contents.  A nice feature is that it also pops open your IE browser's Print dialog. 

 

If you're a developer, one look at the code and you'll likely come up with more ideas for taking this approach in other directions.  On one publishing site, I only wanted a sub-section of the page to be printed, so I simply added a custom DIV to my Page Layout files and modified the script accordingly.

 

The solution comes with a web part that generates the print link, but you can also easily add the link to your master page directly, and completely control how and when it displays. 

 

Thanks to the folks at IOTAP for coming up with a simple, clean approach to printing SharePoint pages.

By: Daniel Herzog | Posted: March 30, 2010 at 9:00 PM
 

Since the advent of free SharePoint Designer (SPD) downloads, many of us have wondered how to block users from using SPD on some SharePoint sites.  While this may fly in the face of empowering your users, there are some (maybe a lot?) of sites that you just don't want anyone to mess with.  SPD can be a powerful tool, and in the wrong hands it can lead to way too many support tickets and site admin headaches.

 

If you've been lucky enough to get to try to resolve this, you've no doubt already read the guidance from the SPD Team at MS, and if you're anything like me you found the solutions it presents less than perfect for your scenario. 

 

Luckily there's another solution available to SharePoint developers that's making its way around the web, with a great example found on CodePlex by iwkid.  Basically there's a property that can be set in an SPWeb property bag, and that will block SPD from accessing your site. 

 

The critical lines of code are:

 

spWeb.AllProperties.Add("vti_disablewebdesignfeatures2", "wdfopensite");

spWeb.Update();

 

I'll spare you all the implementation details - suffice it to say just get a handle on an SPWeb object, set the property, then update the SPWeb object.  You can use what's in the CodePlex solution or you can use this code sample to roll your own.  You can create a utility exe to run thru your entire site collection, create an administration web part to turn SPD access on or off to a particular site (or a bunch of sites at once), or like in the CodePlex example create a Site Feature that can be turned on or off.  Or anything else you can do in code - options are endless.

 

This solution was a life saver, and has worked out very nicely.  We decided to implement it as a Site Feature - using the CodePlex solution as a starting point - allowing site admins to decide whether to grant or deny access.  So far so good, we have not noticed any impact on any other functionality - InfoPath forms still work, which was the downfall of a previous solution we had tried.

 

Another thing I love about this solution - SPD gives a friendly little message to the user when they try to open a blocked site:

"The web site has been configured to disallow editing with SharePoint Designer.  Contact your web site administrator for more information."

 

Nice.

By: Daniel Herzog | Posted: March 24, 2010 at 10:05 PM
 

I'll describe a solution where we leveraged the SharePoint Content Migration API inside a Feature receiver to automatically generate exact copies of existing sites on demand.

 

We recently had a requirement to create multiple new site definitions, with the wrinkle thrown in that the users should be able to change a site definition on-demand without intervention from IT.  The site definitions were to be used across dozens of site collections.

 

Ideally the users wanted to be able to configure a 'template site' somewhere in SharePoint, and have that site serve as the template for any site created with the given definition.  The users could then update this template site, and any future sites based on that definition would essentially be copies of the template.

 

This presented a challenge, because it's a bit different than how SharePoint normally works.  Normally you decide what the site definition will include, and build your definition accordingly.  The definition then lives in the 12 hive, and any changes require an update to the definition files. 

 

Another option of course is to back the template site up as an STP template file, and make that available in the Site Template gallery.  This is a good option to allow fairly easy site template updates (at least there's no server file updates), but it just wouldn't work well in this particular scenario:

  • In an STP, publishing pages tend to get customized (unghosted), we did not want this
  • An STP file has a low size limit
  • The end users would need to understand how to create a new STP, and then distribute the updated STP across the many site collections
    • If we would have gone with an STP-based solution, we would have built a utility to simplify this process.

 

SharePoint provides another way to create duplicates of existing sites, using the Backup & Restore functionality, creating a CMP file.  CMP's have advantages over STP's in that there's no customization of pages, a higher size limit, and most importantly we could automate the process to be totally seamless to the end users.

 

The basic solution consists of the following components:

  1. A site definition, defining a mostly blank site
    1. We can have multiple 'configurations' in a single site definition, useful for our multiple site
    2. Each configuration will display on the "Create New Site" page as a site template option
  1. A Feature
    1. Include this feature in each site definition configuration
    2. The Feature is empty, except that it calls a Feature receiver class
  1. A Feature receiver class - this is where the work is done
    1. This class will look for the template site, create a backup CMP of the template, and then restore that site on top of the newly created site.
    2. Leverages the Content Migration API in the SharePoint object model
  1. A site collection to house the template sites
    1. Along with some housekeeping bits

 

OK, so let's review each component in a bit more detail….

 

The site definition's ONET.xml file contains multiple  configuration definitions, each looking essentially like the below.  This example is for an "Accounting" site, and the custom Feature is in bold: (… is just some standard Features)

 

    <Configuration ID="0" Name="Accounting">

      <Lists />

      <Modules>

        <Module Name="DefaultBlank" />

      </Modules>

      <SiteFeatures>

       

      </SiteFeatures>

      <WebFeatures>

       

        <!-- COPY SITE TEMPLATE FEATURE-->

        <Feature ID="A32F2EC8-D07A-4c6c-AC09-6FD91BA15555">

          <Properties xmlns="http://schemas.microsoft.com/sharepoint/">

            <Property Key="TemplateSiteName" Value="Accounting"/>

          </Properties>

        </Feature>

      </WebFeatures>

    </Configuration>

 

Notice the "TemplateSiteName" property - the Feature receiver uses this value to determine which Template site to use as the source for the site being created.  And don’t forget to create a new webtemp xml file to make these available!  http://msdn.microsoft.com/en-us/library/ms447717.aspx

 

The Feature's Feature.xml file is pretty simple.  Notice we do set Hidden="TRUE" to avoid having the Feature appear in the Feature gallery.

 

<?xml version="1.0" encoding="utf-8" ?>

<Feature Id="A32F2EC8-D07A-4c6c-AC09-6FD91BA15555"

  Title="Source Site Copy"

  Description="This feature copies a source SPWeb into a new SPWeb."

  Version="1.0.0.0"

  Scope="Web"

            Hidden="TRUE"

            ReceiverAssembly="PBCustomCode.FeatureReceivers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9f4da00116c38e98"

            ReceiverClass="PBCustomCode.FeatureReceivers.SiteCopyReceiver"

  xmlns="http://schemas.microsoft.com/sharepoint/" >

</Feature>

 

 

The Feature receiver class is where the bulk of the work takes place.  It's custom code running in an assembly.  For a good overview of how to create a Feature receiver, check here:  http://www.nnihlen.com/blog/archive/2006/12/04/724.aspx

 

When a user chooses one of our custom site definition configurations, the class executes when the Feature is activated, which happens automatically during site creation.  Here's a breakdown of the FeatureActivated method in our custom class:

 

  1. Find the Template site
    1. We have a custom SharePoint list at a known location in the Template site collection, with 2 colums:  the Template Site Name and the URL of the template site.
    2. Doing a CAML query on this list, using the TemplateSiteName property value as a key, locate the URL of the template site.
      1. string sourceTemplateName = properties.Feature.Properties["TemplateSiteName"].Value;

 

  1. Generate a CMP backup of the template site
    1. Find a good example of how to export to a CMP and then import it here: http://msdn.microsoft.com/en-us/library/aa981161.aspx
    2. You'll want to save the CMP to a known location on the server.  I name it with a GUID, and save it to a custom folder in the 12 hive.  I also delete the file once I'm done with it.

 

  1. Import the CMP file right on top of the site we're currently creating
    1. See the same example of how to export to a CMP and then import it here: http://msdn.microsoft.com/en-us/library/aa981161.aspx
    2. Import it directly into the URL of the new site.  Add this line to the code, just after the line importSettings.SiteUrl = destinationSiteUrl; :

importSettings.WebUrl = web.ParentWeb.Url;

 

  1. A few important things to keep in mind:
    1. The SPWeb.Name value of the new site must be the same as the source site.  It’s unlikely that your user happened to choose the same name as the template author, so we simply temporarily rename the new site with the same name.  We'll change it back later.
      1. To get a handle on the new site's SPWeb object:

SPWeb newWeb = (SPWeb)properties.Feature.Parent;

  1. Both the Export and Import process should be run with SPSecurity.RunWithElevatedPriviliges
  2. Both the Template site and the destination site must have been created with the same site definition configuration
    1. So, keeping with our Accounting example, we need to create the Template site (in the Template Site Collection) by using the Accounting site template.
    2. When you create a Template site, you don't want your Feature receiver class to execute.  We avoid this by having the Feature receiver first check the URL of the current SPSite object.  If it's the same as the known Site Template Site Collection, we skip execution of any actual code.  This allows the Template site to be created without any complications.

 

  1. Cleanup
    1. Delete the CMP file
    2. Rename the SPWeb.Name to the original value (as created by the end user)

 

 

OK, now you're ready to try it out:

  1. In your site template site collection, create a new site using one of your custom site definitions
    1. Configure the site with lists, libraries, web parts, permissions, etc.
  1. Now, in a different site collection, create a new site using the same custom site definition
    1. The Feature receiver should activate. 
    2. The code finds the URL of the template site, based on the TemplateSiteName property in the configuration definition.
    3. It will generate a CMP file at the location you specify.
    4. The code will temporarily rename your new site with the same name as the template site
    5. The CMP file will be imported right on top of your new site
    6. The code will rename your site to the original name, and delete the CMP file
  1. When the site creation process completes, you should have an exact duplicate of your template site.

 

I hope someone finds this approach useful, it has served us well so far. 

 

By: Daniel Herzog | Posted: December 30, 2009 at 11:46 PM
I recently had to create a custom web part that could consistently display list items in a consistent manner, and it had to allow the user to point it at any list with any filtering and sorting criteria.  Rather than build my own filtering and sorting funcitonality, I found it easier, more user-friendly, and more flexible to leverage SharePoint's list Views.
 
The idea is simple - let the users create a view, complete with sorting and filtering rules, and then use that as the basis for selecting the items to display.  The SharePoint object model makes this easy.
 
The key is building an SPQuery object from an SPView object:

using (SPWeb rootWeb = SPContext.Current.Site.RootWeb)
{


 //get list view
 SPList theList = rootWeb.Lists[_listName];
 SPView theView = theList.Views[_viewName];
 
 //get the query, based on the view
 SPQuery theQuery = new SPQuery(theView);

 //run the query, get the items collection
 SPListItemCollection viewItems = theList.GetItems(theQuery);

 //build a display for the items in the collection
 ...

}
By: Daniel Herzog | Posted: December 30, 2009 at 11:12 PM
SharePoint Designer 2010 makes it easy to create a workflow.  What's even better is that you can export that workflow, import it into Visual Studio 2010, and add code and custom activities.
 
You may find that exporting the workflow to a WSP in SharePoint designer fails.  Luckily there's a simple workaround once your workflow is deployed to a SharePoint 2010 site.
  • Go into that site and save it as a template (via Site Settings). 
  • Then download the WSP from the site template gallery. 
  • Now import the WSP into Visual Studio 2010, using the "Import Reusable Workflow" template.

I have found this workaround to work nicely. 

By: Daniel Herzog | Posted: October 19, 2009 at 3:08 PM
SharePoint 2010 is official!  The SharePoint Conference is underway in Vegas.
 
Pointbridge has posted quite a few SP2010 blog entries - see them here:
 
 
I contributed three so far myself:
  1. Quick Managed Metadata Overview
  2. Enforcing Relationships in Lookup Columns
  3. Column Validation in SharePoint 2010

 

By: Daniel Herzog | Posted: March 24, 2009 at 7:51 PM
 

We had a page with a few custom web parts on it, and some users would get this error message while others did not.  Obviously since some users didn’t get the error, the lists must all at least exist, so I first assumed it was probably a permissions thing on one of the lists being accessed by one of the custom web parts. 

 

After an exhaustive check of permissions on every list in the site, I was able to rule that idea out.  The message "List does not exist" didn't seem to be exactly helpful.

 

With some trial-and-error I was able to narrow it down to one web part, so I opened the code and started walking thru it.  I finally found the culprit… in one section of the code I found something like this:

 

SPUserCollection groupUsers = theGroup.Users;

 

So you see the code is trying to get a list of all the users that belong to a particular Group.  In this case, different users would get different values for theGroup, and it would not always be a group they belonged to.   Here's where it would break.

 

If the current user didn't belong to the Group, the Group would need to allow Everyone to see it's members, which is luckily a property of a SharePoint Group - just go to the Group and select "Group Settings" from the "Settings" dropdown:

 

Group settings

 

Once that was set to "Everyone" on the Groups, the problem went away.  Of course, another (possibly better) way to fix this would be to run the code under elevated permissions.  But at least I got to the bottom of the error.

 

And the lists did exist :-)

By: Daniel Herzog | Posted: March 23, 2009 at 8:10 PM
 

This page contains both secure and nonsecure items

 

nonsecure message

 

Since you may be running SharePoint in a secure (HTTPS) environment, such as for a corporate intranet, you may encounter this popup warning message from time to time.  Of course, it's not actually an error message, but users will find it annoying and may regard it as an error.

 

More often than not it's something simple, like an image pulled in from an external nonsecure site, or an iframe pulling in a nonsecure site.  Sometimes though, it's just plain weird and tough to uncover.  Here are a few i've run into...

 

  1. The Menu control

 

On one page we had a custom web part, and the web part happened to use a Menu control.  It contained one visible node, and when a user hovers over the node a flyout appears with the subnodes.  We found that in IE6 browsers, when the user would hover over the visible node in the Menu control, the "secure and nonsecure" warning popup would appear.  It didn't seem to matter whether the user would select "Yes" or "No", the control would end up working fine.

 

This was maddening to resolve, but I eventually ran into the explanation from Jerry Ormon at MS.  It seems that the Menu control uses a hidden iframe, which points to "about:blank", which IE6 sees as nonsecure.  http://support.microsoft.com/kb/910444  Thankfully, it's pretty simple to resolve - just register a startup script that overrides the iframe URL with the blank.htm file that lives in the _layouts directory. 

 

Add something like this code to your web part's Render override:

 

string ie6fixJS = menuControl.ClientID + "_Data.iframeUrl='/_layouts/blank.htm';";

Page.ClientScript.RegisterStartupScript(typeof(this), "MenuHttpsWorkaround_jll", ie6fixJS, true);

 

Note that we did not see this behavior with IE7, only IE6.

 

  1. Redirect in dropdown OnSelect event

 

Again, with IE6 only.  We didn't see this problem in IE7.

 

We have a page that has a dropdown list, allowing the user to select a project name.  When a selection is made in the dropdown, the server-side event handler redirects the user to the proper SharePoint Publishing subsite.  Of course, since it's a SharePoint Publishing site, we're not redirecting directly to a page, but to the site's URL, such as https://moss/subsite1.  As you know from experience, when you hit the site's URL, SharePoint will redirect you to the welcome page of that site, perhaps https://moss/subsite1/Pages/home.aspx.  This is where it gets nasty. 

 

SharePoint handles that welcome page redirect by issuing 301 responses, which essentially tells your browser to make another request to the full welcome page URL.  When the redirect happens in the course of an OnSelect event from a dropdown list, you'll find that one of the 301's issued by the server is nonsecure (HTTP instead of HTTPS).  Yuck.  You have a couple of options at this point.

 

First, you can change your redirect code to go to the full welcome page URL.  You can do this pretty easily by getting an PublishingWeb object of the site you're redirecting to, then inspecting that object's DefaultPage property.

 

Another option is to not do a server-side Response.Redirect at all, but use the old HTML META Refresh method:

 Response.AppendHeader("Refresh", "0; URL=" + url);

 

This will cause the page to post back, then add a tag to the response page that will instruct the browser to redirect to the URL in the tag.  Problem solved.

 

  1. RSS Feeds

 

This one can be a problem in IE6 or IE7. 

 

Your users may find the RSS Viewer web part to be very useful - and it is for the most part.  But when you're in an HTTPS secure environment, and a user points the RSS Viewer at some external feed, the data returned by that feed may contain embedded HTTP resources, such as an image tag with it's source pulling in from a nonsecure site:

<IMG SRC="http://www.somesite.com/demo.jpg" />

 

Here's a fairly simple quick'n'dirty workaround…  create a new page in the _layouts directory, called something like rssFilter.aspx.  The page will get the RSS feed and then parse out wherever it sees a SRC property with a non-secure value.  It then re-serves the updated feed.  Now, train the users to point the RSS Viewer to:

 /_layouts/rssFilter.aspx?rssUrl=<rss url>

 

 

I'm sure there are other solutions to the above problems, but these worked nicely for us. 

By: Daniel Herzog | Posted: February 18, 2009 at 8:55 PM

A few days ago we ran into a situation where a user had uploaded dozens of documents into dozens of libraries on dozens of sites.  The problem was that the user had uploaded multiple documents at a time into libraries that had multiple content types, so none of these documents were checked in.  Since they had NEVER been checked in, the documents were only visible to the user who had uploaded them.

SharePoint offers a handy feature (Site Actions > View Reports > Checked Out To Me) where that user can see the documents checked out to them, and even check them in all at once.  This is great, but in our situation since permission inheritance was broken on each of the subsites, there was no way to view all the documents across all the sites, just on one site at a time.

So, here we are faced with the daunting task of manually going thru dozens of sites looking for documents to check in.  As a developer, your first instinct is to turn to a programmatic solution to avoid this huge manual process.

We needed a simple utility to look for items that had never been checked in across all sites in the collection, and check them in.  Just a little console .exe...

First get a handle on the SPSite object:

using (SPSite mySite = new SPSite(siteCollectionURL))

Now you need to impersonate the user who uploaded the documents.  Only that user can see those documents.  A site collection admin can't.  The app pool account can't.  Only the original uploader.

SPUser impersonateUser = mySite.OpenWeb().AllUsers[@"DOMAIN\user.name"];
SPUserToken token = impersonateUser.UserToken;

Now get a new SPSite object.  All objects you create from the SPSite will run under the impersonated account.

mySite2 = new SPSite(siteCollectionURL, token);

Then loop thru the SPWeb's:

foreach (SPWeb myWeb in mySite2.AllWebs)

Look for SPFolder's:

foreach (SPFolder myFolder in myWeb.Folders)

Check each SPFile to see if it's checked out:

foreach (SPFile myFile in myFolder.Files)
{
 if (myFile.CheckOutStatus != SPFile.SPCheckOutStatus.None)

And check it in:

myFile.CheckIn("Checked in by utility.");

If you have publishing set on the document library, you may want to publish it too:

myFile.Publish("Published by utility.");

The only piece of this that surprised me (but probably shouldn't have) was the need to impersonate the original uploader. 

I should also note a few caveats:

  1. The above code will check in all files checked out to the impersonated user - not just the ones that have never been checked in.  If you want, you can inspect the Versions collection on the SPFile object to see the history.
  2. The above code will also check in pages in the Pages library if you have a publishing site.  You can add a conditional to skip over the Pages library in your loop if you want.
  3. I found that to work properly, my impersonated user had to be a Site Collection Admin.

 

By: Daniel Herzog | Posted: March 30, 2008 at 10:07 PM
I was recently having an IM discussion with a colleague that reminded me of something I discovered a while back.  It's pretty simple, but I thought I'd post it here in case it helps anyone.
 
In a custom ASPX page in SharePoint, you can get the nifty Cancel button to show up (and actually be functional!) without writing any code. 
 
Assuming you have a Save button that executes custom code, in your ASPX file:
 
<wssuc:ButtonSection runat="server">
 <Template_Buttons>
  <asp:PlaceHolder ID="PlaceHolder1" runat="server">
   <asp:Button runat="server" class="ms-ButtonHeightWidth2" OnClick="BtnSave_Click" Text="Save" id="btnSave" />
  </asp:PlaceHolder> 
 </Template_Buttons>
</wssuc:ButtonSection>
 
This will automatically include a Cancel button whose behavior is to redirect to the URL defined in the "Source" QueryString parameter.
 
Nice!
By: Daniel Herzog | Posted: March 29, 2008 at 10:43 PM
The constraint of only allowing a single instance of a given workflow to run at a time on a list item may run right up against a real-world scenario in which multiple concurrent instances may be required. 
 
The following workaround may help you resolve this problem.  On the face of it, its really quite simple: dynamically generate additional workflow associations on the list as needed.
 
High level explanation:  A workflow is associated with a list, making the workflow available to that list.  There's nothing stopping you from creating multiple associations of the same workflow, and therefore running multiple instances of that workflow concurrently, which each running under a different association.
 
OOB, you can do this in the UI - creating mulitple associations - as many times as you think your users will need.  The weakness here is that the user will need to understand that the multiple workflows they see available to them are actually just different associations of the same workflow.  Many users might find this confusing, creating more problems than it solves.  Luckily, you can also generate these associations programmatically.
 
 
<Digression>
In other postings, I have discussed the idea of using a non-standard way to start workflows.  Some benefits include ease of support for new versions of the workflow and more control over how the user interfaces with the workflow. 
 
Another benefit would be providing the user with a single point of interaction for starting the workflow, and then programmatically handling the logic of either using an available association or generating a new one.
</Digression>
 
 
Example:  You have a simple workflow called "MyWorkflow" associated with a list called "MyList".  A user clicks your custom "Start MyWorkflow" link for an item in MyList, and your custom 'start workflow' code kicks in and determines that the single association MyList has for MyWorkflow is currently being used.  The code then generates a second association of MyWorkflow on MyList, and starts the workflow using that association.  Later on, the instance running on the first association completes.  After that, a user starts MyWorkflow on that same item.  Your code then determines that there is an existing available association, and starts MyWorkflow using that association.
 
Notice that ideally the code only creates new associations when it has to - only when all the available associations are being used.  Otherwise it will use the first available association it finds.
 
To get the collection of associations on the list:
SPWorkflowAssociationCollection WfAssociations = theItem.ParentList.WorkflowAssociations;
 
To determine if any association is available, loop thru this collection, only looking at associations with the BaseTemplate.Id value corresponding to the workflow you're interested in.
 
To test an association for availabitlity, loop thru the workflows on the current item, and check the status:
if (currentWF.ParentAssociation.Id == wfAssociation.Id && currentWF.InternalState == SPWorkflowState.Running)
If needed, you can generate a new association:
SPWorkflowTemplate wfTemplate = Web.WorkflowTemplates[wfTemplateId];
SPWorkflowAssociation newListAssociation = SPWorkflowAssociation.CreateListAssociation(wfTemplate, associationName, taskListName, historyListName);
MyList.AddWorkflowAssociation(newListAssociation);
Where wfTemplateId is the Template ID of your workflow, and associationName is a uniquely generated name for the new workflow association.  Something like the original workflow association name, with an appended _1, _2, _3, etc.
 
Happy coding, and I hope you find this workaround useful.
 
 
                    
By: Daniel Herzog | Posted: September 27, 2007 at 9:49 PM
It's pretty straightforward to update a site Content Type in SharePoint and have those changes cascade down to the lists that inherit from the Content Type using the SharePoint UI.  The challenge comes in when your Content Type is created from a custom definition file, and you expect to use that definition file to create more site Content Types in the site collection at a later time. 
 
I spent some time with various scenarios, and came up with a process that worked consistently.  It may look bit odd, but it worked nicely.
 
First, let me spell out the situation.  We had:
  1. custom Site Columns
    1. Defined in MyColumns.xml
  2. a custom Content Type
    1. Defined in MyCustomCT.xml
  3. a custom List definition
    1. Defined in a custom schema.xml
The site columns were referenced in the Content Type, and the Content Type was referenced in the List definition.  All were deployed as SharePoint Features to a site collection.
 
We had to update the definition files so they could be used by custom site templates to create more sites, and they simultaneously had to update the existing sites and lists with some changes - including datatype changes and new columns.
 
The first approach was simply to update the definition files and redeploy.  Sadly this was too simple and just didn't work.  It even broke the lists.  With some experimentation (errrr... trial and error....) we were able to update the files, update the existing sites and lists, and keep everything working.
 
It became apparent that an approach that combined updating the definition files for future use and updating the existing columns, content types, and lists via the UI was going to be needed.  The trick was to pull this off without breaking anything.
 
Basically, we solution we came up with was to do this in 3 steps:
  1. Unseal the columns (if necessary) and update the datatypes in your Site Column and List definitions
  2. Add new columns to Site Column and Content Type definitions
  3. Add new columns to List definition

And here's a more detailed look into it:

  1. Backup your existing definition files.
  2. Ensure all columns (or fields) that you want to update are not sealed in the Site Column and List definition files.  If any are, you must first change the definition file to unseal them.  (Sealed="FALSE")
  3. Make any datatype changes to these 2 files.  Then re-deploy the Features.
  4. You may notice that the datatypes didn't actually change for the existing Site Columns on your site.  You can make data type changes to the existing Site Columns via the SharePoint UI at the site collection.  Be sure that the answer to "Update all list columns based on this site column?" is Yes.
  5. Now go ahead add the new columns to the Site Column and Content Type definition files (not the List definitions yet).  Redeploy these Features.
  6. Verify that nothing is broken.  If so, revert to your old definition files and determine what is wrong with your new ones.
  7. You'll probably notice that even after re-deploying these Features, the new columns you tried to add did not get pushed down to the lists.  You'll have to do this yourself, and here's an easy way to do it: 
    1. Go to the parent Content Type - probably at the site collection level
    2. Find the new columns you created.
    3. Delete them.
    4. Click "Add from existing site columns"
    5. Add them back - make sure "Update all content types" is Yes
    6. This should add the columns to your existing lists that inherit from this Content Type
  8. Now, if you define the columns in your custom List definition, you can update your List definition file with the datatype changes and new columns.  Redeploy this Feature.
  9. Verify that all existing lists contain the columns and datatypes you expect.
  10. Create a new list based on the content type.  Ensure the list contains the columns and datatypes you expect.

This worked perfectly for us.  If anyone has a different (simpler???!!!) approach, I'd love to read about it in the comments.

 

By: Daniel Herzog | Posted: September 27, 2007 at 9:03 PM
On a large SharePoint project with multiple custom components, we reached a point where we had to reorganize our projects and solutions to maintiain a tighter order on the project.  In the process, the project files were moved - some were rebuilt, some replaced, some consolidated.
 
One project was a custom SharePoint workflow that included a custom Activity living inside a Replicator Activity.  The Replicator Activitiy had some custom rules, which of course were stored in the .rules file. 
 
When we tried to recompile the project, Visual Studio kept insisting that it couldn't find the condition 'AllTasksComplete', which we recognized as the custom condition we had set.  The rules file was there, and had the condition intact.  Very frustrating.
  • Activity 'replicatorActivity1' validation failed: Can not find the condition "AllTasksComplete".
In the end, we found that somehow in all the moving around of the Project files, the .rules file became disassociated from the workflow .cs file. 
 
It's simple enough to reassociate it:
  1. Open the .csproj file in Notepad
  2. Find the section where the .rules file is referenced.  It will look something like this:
    • <EmbeddedResource Include="MyWorkflow.rules" />
  3. Alter this tag to include the DependentUpon tag:
    • <EmbeddedResource Include="MyWorkflow.rules" >
            <DependentUpon>TheWorkflow.cs</DependentUpon>
      </EmbeddedResource>
  4. Save the .csproj file
  5. Recompile the solution

This should do the trick. 

By: Daniel Herzog | Posted: June 11, 2007 at 6:41 AM

A shorter day today - and the last day of a whirlwind week in Orlando.  I caught sessions on SharePoint Designer, LINQ, and a great security presentation that was both fascinating and a bit scary.

SharePoint Designer is one of those tools, like its predecessor FrontPage, that I think many developers may look down upon - you know, "real developers don't use this."  I'm now convinced that it does have its place in even the most hard-core SharePoint developer's toolkit.  You can learn to use it pretty quickly, and can use it to develop no-code applications that are free of the risks of installing custom code on to your production servers.  It also has the powerful Data View Web Part, which has an easily customized UI and can combine data from multiple sources including SQL, RSS, XML, and the Business Data Catalog, among others.  I'm still not convinced that it can be the primary tool for developing large SharePoint applications that have heavy customizations and many sites based on those customizations (the one-list-only workflow tool comes to mind), but I do plan on spending more time with SharePoint Designer to leverage it's benefits.

.NET Language Integrated Query (LINQ) is a new feature of .NET, and it looks like this really might change the way you code applications in a pretty significant way.  Essentially LINQ allows you to query .NET collections of just about any type using a SQL-like syntax.  Rather than using any other way you may have used in the past to search for data in a collection, you can replace it with the execution of a simple query.  The upshot is the promise of simpler, more elegant, and less code.

Marcus Murray demonstrated how easy it can be to hack a server.  Word about this guy was spreading around TechEd like wildfire - his sessions were very popular.  In the demo I saw he showed us how simple it can be to brute-force hack an FTP server and write a buffer overload to execute nasty code.  The upshot of his presentation is that you can protect yourself against attacks by understanding potential threats, configuring your systems for security, and keeping your systems updated with the latest patches.  A very entertaining, and somewhat sobering session.

Wow - what a week.  All that was left to do was head to the airport, hold tight thru a flight delay, and spend 3 hours in the air with my knees jammed into the metal bar of the magazine pocket on the seat in front of me.  A fantastic week behind me, and some great ideas for the work that lies ahead.

By: Daniel Herzog | Posted: June 7, 2007 at 6:36 AM
I managed to cover quite a bit of material at TechEd on Thursday - a bit about upgrading custom sites from WSS 2.0 to 3.0, some good info on SharePoint features and templates, a nice intro to Team Foundation Server, some new ideas for SharePoint workflow, and more vendor tools.
 
As a quick aside, I'm wondering what the more common pronunciation is for "Guid"?  I've always said "goo-id" but most of the presenters here say "gwid".  Feel free to leave a comment with your preference.
 
WSS 2.0 to 3.0:
I know some people have had major challenges upgrading from WSS 2.0 to 3.0.  The Solutions Accellerators group covered a process here for helping to get thru the difficulties posed by customized 2.0 sites.  Of course, this assumes the database upgrade runs smoothly :)  Find the TechNet article here

SharePoint Features & Templates:
The coolest thing about this session by Ed Hild was the idea of kicking off code when a feature is activated.  You can leave certain features deactivated in a custom site by default, and when the feature is eventually activated by the user you can run code to do things like create dependent lists, associate workflows, or do anything else that may be required by the feature.
 
You can also kick off code when a site is created.  I can see a real use for this in a deployment with custom site definitions with webparts or other custom functionality that needs defaults or other input set dynamically with data only available when the site is being created.  You may not want to count on certain variable data to be available from another source (like the URL) that you can set at create time.
Keep an eye out here  - Ed said he'd be posting code samples.

TFS:
Team Foundation Server (TFS) is the server product of Visual Studio Team Services.  The first thing that may pop into your mind when you hear TFS is source control, which it does do nicely, but it is more powerful than that.  Use it for:
 work item (issue) tracking
 version control
 build automation
 project portal (based on SharePoint)
 reporting (based on SQL Reporting Services/SQL Analysis Services)
Wrox has a TFS book available. 
Also, check out Brian Harry's blog
and the TFS dev center
 
SharePoint workflow:
I've had some pretty extensive exposure to SharePoint workflows, but Ted Pattison and Scott Hillier's presentation got me thinking about an issue I've had with the way workflow is implemented in SharePoint.  The problem is that you can only have one running instance of a given workflow on any given SharePoint item.  I can envision many scenarios where you may want to allow the user to run more than one instance at a time.  My initial thought was to build the workflow in such a way that it can dynamically create multiple instances of each task in the workflow, probably via Modification forms.  I think this may work with certain, simple workflows - but it seems like a lot of work and something that you'd need to build deeply into the design of the workflow from the outset.  But something simpler came to mind while watching their demos - you can simply run code that checks for a current running instance, and if one is there, dynamically create a second association of the workflow template to the current list.  In the future, you can use this new association whenever a running instance exists, and can create more associations when needed. 
 
Of course, you may have a bit of a training issue with end users, but this could even be transparent to the end user when you use the solution I describe here coupled with a solution that supresses the default workflow columns in the list default views.  Just a thought, and I haven't tried it yet, but it should be workable.
 
Apologies for being a bit tardy on this entry, but I should also get Friday's review up today as well.
 
By: Daniel Herzog | Posted: June 6, 2007 at 10:45 PM
Advanced Web Parts and Custom SharePoint field types were on tap today at TechEd. 
 
Andrew Connell held a great session on Building Advanced Web Parts with ASP 2.0.  He gave some examples of building advanced features in Web Parts...
  • How to customize the Web Part 'Verbs Menu.' 
  • How to build an asynchronous Web Part in SharePoint, even though SharePoint doesn't support it.
  • Building connected Web Parts - its much easier in SharePoint 2007 than it was in 2003. 
  • Code Access Security Policy.

Andrew said he would be posting the code samples to his blog.  I look forward to seeing it again - this is really good stuff.

Todd Bleeker gave an excellent session on building Custom Field Types in SharePoint 2007.  If you haven't seen Todd in action, he's really enthusiastic and it makes for a great presentation.

Its pretty easy to imagine a situation where you need to store data in a SharePoint list or library that doesn't neatly conform to one of the out-of-the-box field types.  So what to do?  Create your own filed type!  You'll need a custom field type class and a custom field type definition (.xml), based on the fldtypes.xml file found in \TEMPLATES\XML.  I can't reproduce his code here, but this a really interesting area to investigate - I'm interested in looking into it further.

One interesting field type that many developers might miss even exists is the SPFieldMultiColumn.  It is acessible via the object model only - not the UI.  It's functionality may be useful for some situations - its worth a look.

Another interesting thing Todd pointed out is the concept that you can actually store a custom field type's data somewhere outside of the content DB.  I can imagine a use for this when storing some highly structured data in a single field.  He didn't have an example, but just the idea that it can be done presents exciting possibilities.

Back at the vendor booths were more free t-shirts and some good conversations with companies doing interesting things with SharePoint.  echoTechnology has wizard-based tools for migrating to SharePoint 2007, and other SharePoint tools for doing things like migrating from dev to stage to prod, and for taking a workflow developed in SharePoint Designer and adding it to any number of lists across your SharePoint environment. 

Proposion has tools for migrating from and integrating with Notes.  They actually have a tool to use as a ADO.NET data driver for Notes, allowing you to develop apps against Notes data.

2 days to go - and some very promising sessions ahead.

By: Daniel Herzog | Posted: June 5, 2007 at 10:13 PM
MOSS Search, InfoPath, and more SharePoint were on tap today.  Here's some highlights.
 
Search is one of the most powerful features of MOSS, but as a developer -even one with a ton of MOSS experience, thank you very much - it isn't something I've spent too much time working with.  It was great to get some exposure and advice on search configuration today.
 
Here's the configuration basics as I understand it now - if anyone finds anything that needs correction, please leave a comment!  The two key server roles in search are Indexer and Query.  The Indexer creates the index, and the Query consumes it.  With WSS, both roles live on the same box; with MOSS, you can split the Indexer from the Query server.  If you do split them, the Index server must be dedicated - no other SharePoint services can run on it.
 
You can have multiple search servers in WSS, although each of your content DB's can only connect to a single search server.  This means that the search servers are not redundant, so this won't improve availability.  With MOSS, your search servers can connect to multiple content DB's, so the redundancy improves availablity.
 
Some suggestions to improve performance include using 64 bit servers, separating the Query service from the WFE's, and using dedicated load-balanced WFE's.  Optimizations to consider include the crawl, authoritative/demoted pages, weight of properties, the ranking formula, and managing metadata - for example, Title and Author are important fields for search, so consider making them required. 
 
On to InfoPath.  Did you know there's an importer for Word and Excel documents?  I didn't, and I still haven't tried it, but from the demo it looks like they may be useful to help convert existing paper forms into InfoPath.  At least they'll give you a start. 
 
I've been pretty proud of some of the managed code I've written for InfoPath 2007 forms, but now I'm told it's better to avoid managed code in InfoPath wherever possible - especially in On_Load.  There's still a place for it, but only when necessary.  If you have a ton of managed code, consider skipping InfoPath in favor of a custom application.
 
Adding your InfoPath form to an ASPX page is farily straightforward.  Just publish your form to a Forms Server (like SharePoint) and build an ASPX page with the XMLFormView control. 
 
I also attended some discussions on MOSS implementations.  One centered around using MOSS as a platform for building applications, something I guess we've been doing on our document management system project whether I realized it or not.  People have encountered some common challenges, and I was very happy to realize that we did a pretty darn good job in resolving them. 
 
Another good day - 3 more to go!
By: Daniel Herzog | Posted: June 4, 2007 at 8:10 PM
A successful Monday.  Aside from free t-shirts (ultra-stylish?  you bet!) and other swag, there were some good sessions, and a fun Keynote by Bob Muglia.
 
The Keynote started with a 'Back to the Future' thing - with Christoper Lloyd, the DeLorean, and everything.  The joke was that MS's "visions" of the future were getting a bit stale, so let's concentrate on what we can do today in the real world.  Bob wanted to drive home the point that organizations should be able to look at IT as a strategic profit center rather than a cost center.  Certainly not a new idea, but one that he feels MS can really help deliver.  To that end, MS has developed Optimization Models to help guide the organization.
 
Another theme of the Keynote was the importance of agility - not just in the IT organization, but in the entire organization.  Agility is essentially the ability to sense and respond to change with speed and efficiency.  It is critical for the future of an organization, and important for making IT strategic today.  Agility has to exist across an organization, from IT to business processes - and constant communication among the technologists and their clients and stakeholders is paramount.
 
After the Keynote, I attended a session on Groove 2007.  Being interested in collaboration, this is a pretty interesting tool - one I look forward to playing around with soon.  A key feature to me was the ability to synch SharePoint Document Libraries without having to provision all Groove users with AD and SharePoint credentials.  Groove has its own credential system, so it can serve as a means to collaborate on documents with partners external to the organization.  I wish it would integrate other SharePoint features, such as lists, but maybe in the next release.
 
I also attended a very crowded session on Agile development with Visual Studio Team System.  The key take-away is that MS is jumping headfirst into supporting agile development, and the tools for testing and reporting results are integrated nicely into Visual Studio.
 
The remainder of the day centered around SharePoint, of course - as will much of the rest of the week.  For me the best part was participating in the Hands-on Labs, many of which are available online at http://www.microsoft.com/technet/traincert/virtuallab/office.mspx
 (you'll need a Windows Live ID).  The lab setup here at TechEd makes it really easy to fly thru these labs, and learn the basics of some features you might not have encountered yet.
 
The vendor area is always fun.  One vendor I found very interesting is Colligo - they have a really cool "SharePoint on the Desktop" solution that integrates SharePoint with Outlook, and also provides a client application to view SharePoint site content.  Worth a look.
 
Wow - busy day.  And I'm looking forward to tomorrow.
By: Daniel Herzog | Posted: June 3, 2007 at 9:02 PM
I arrived at TechEd 2007 in Orlando last night, and today I has the privilige of attending a pre-conference seminar on Software Architecture given by Ron Jacobs.  Ron is a very sharp guy with some great and unique insight into just what software architecture means.  Check out his radio and TV shows to hear it from him firsthand.
 
You can download his presentation slides from his site.  The seminar was an all-day event, and he covered quite a few topics in some detail.  Overall, I left with a few key take-aways about architecture and the role of the architect...
 
What is software architecture? 
  1. The overall structure of the system components
  2. The public interfaces of the components
  3. The relationships among the components
The developer and the architect are actually very different roles requiring different skill sets.  Being great at one doesn't mean you'll be successful at the other.  The architect has a broad width to his understanding of available technologies.  Depth isn't quite as important - that is the domain of the developer.
 
The architect has 3 primary roles to play:
  1. Explorer - looking forward to new technologies that may be leveraged to transform business
  2. Designer - create scaleable, secure, useable, and appealing applications
  3. Advocate - ALWAYS serve the needs of the client.  LISTEN.  And be brutally honest - even if its not what the client wants to hear.

Communication, to both technical and non-tecnhical audiences, is the #1 skill of a successful architect.

The architect creates goals and boundaries for the developers to work within.  He will define the problem to be solved (along with the client) and will survey the existing constraints. 

A clear architecture document is important (maybe critical?) to define the problem to be solved, the scope of the solution, and the overall architectural plan for implemetation.  It should define very specific goals for security, availablility, and performance.

Once an architecture is planned for a system, it is important to have an architectural review, where the plan is reviewed by colleagues to uncover any shortcomings.  This is not done very often, but it has been shown that a review can catch a crtical flaw that may cost time & money down the road, or even uncover an unseen showstopper that may have killed the project. 

Thanks to Ron for a great class.  I'm looking forward to the rest of the week.

By: Daniel Herzog | Posted: April 3, 2007 at 4:37 PM

When trying to use a SharePoint object (ie SPWeb) within a elevated privileges, you can't just re-use an existing object you created outside the block - it will fail.  You need to create a new instance of the object within the elevated block. 

In this example I'm assuming you are first getting the SPWeb object using the current Context, probably so that you can do some work with the object.  At some point in the code, you need to elevate the privileges.  You won't be able to use your existing SPWeb object, because it was created with lower privileges. 

I've had success by grabbing the SPSite.ID and SPWeb.ID Guids, and then using them within the block to create a new instance.   

//declare Guids

private Guid spWebId;

private Guid spSiteId;

<...>

//get current site

SPWeb currentWeb = SPControl.GetContextWeb(Context);

//for future reference:

spWebId = currentWeb.ID;

spSiteId = currentWeb.Site.ID;

<...>

//run some code with elevated privileges

SPSecurity.CodeToRunElevated myCodeToRun = new SPSecurity.CodeToRunElevated(myCode);

SPSecurity.RunWithElevatedPrivileges(myCodeToRun);

 

//Here's the code running elevated

private void myCode() {

     //get currentWeb again, using the GUIDs

     SPSite currentSite = new SPSite(spSiteId);

     SPWeb currentWeb2 = currentSite.OpenWeb(spWebId);

     <...>

}

 

By: Daniel Herzog | Posted: March 29, 2007 at 10:21 PM
After taking a bit of a break from workflows to attend to other aspects of MOSS customization, I'm preparing to get back into it.  In digging thru some old files, I found some notes I had made about embedding .NET code in your MOSS Workflow InfoPath forms.  I thought I'd post it in case it might help anyone out…
 
The ability to use InfoPath 2007 to create your workflow forms is one of the nicest features of custom MOSS workflow development.  To make your forms even richer and more powerful, you can add 'code-behind' logic to your form using .NET code.
 
Overall, its pretty simple to write code for an InfoPath form, and that topic is covered very well in many other places, so I won't go into any detail here.  But what I will do is point out a few things I've learned about using InfoPath code behinds in MOSS workflows.
 
1. You don’t need an association form
   a. Just comment out the tag from workflow.xml

2. If you have any control event handlers in you IP code-behind that require a postback, you’ll need to set the control’s Browser forms postback setting to “always”
   a. Under Browser forms tab in the control’s properties dialog
   b. Example, you have an event that fires when a dropdown value changes

3. If you are going to access the SharePoint object model in your code, you'll need to sign the form to make it fully trusted
   a. You don’t need to sign the form unless your code-behind accesses the SharePoint object model

4. You’ll need the code-behind .dll file to live in the Feature directory folder, alongside the .xsn file
   a. Not sure why having it in the GAC doesn’t do the trick
By: Daniel Herzog | Posted: March 29, 2007 at 10:21 PM
Our team has spent a bit of time working with the latest Ajax Control Toolkit, available at http://www.codeplex.com/AtlasControlToolkit, incorporating it into our SharePoint customizations. 

Getting the controls to work in SharePoint requires jumping thru a few hoops – here’s a great article from Mike Ammerlaan at Microsoft to help you get started. 

We quickly became interested in the Update Panel and List Search controls.  The Update Panel allows you to refresh a section of the page without a complete screen refresh – it makes for a faster response and a nicer user experience.  The List Search attaches to drop down controls and, among other things, gives you great type-ahead functionality, allowing you to type your drop down selections.  The goal was to make using the mouse unnecessary, allowing users to fly thru the forms much faster.

To start, we were pretty happy with what we were finding.  We built out a POC incorporating the Update Panel and List Search controls.  (Notice the special section at the bottom of Mike’s article about using the Update Panel.)  It was great because we have a complex custom screen in our SharePoint site that has multiple dropdowns with autopostbacks.  The type-ahead functionality of the List Search combined with the Update Panel’s partial screen refresh made the page much more user-friendly.

A problem arose in the form of a bug in the List Search control.  When typing ahead and tabbing off the control, you would expect the postback event to fire, but it doesn’t.  This has been logged as a bug on codeplex, but it doesn’t look like there’s been any activity on it as of this posting.   We also had challenges with controlling the tab sequence after postbacks, and with getting good behavior out of using the up and down arrow keys in the dropdowns. 
 
Overall these tools are very good, even if they didn’t meet our immediate needs.  I’m sure I’ll be revisiting them in the near future for other solutions.
By: Daniel Herzog | Posted: December 26, 2006 at 10:30 PM

Say you have version 1 of a custom workflow running and being used by your end users, and later on when you have version 2 ready to deploy.  You may have many instances of version 1 in progress, and you certainly don’t want to disrupt these running workflows – they should be allowed to continue running as version 1 until they are complete.  Likewise, you want any new instances of the workflow started by end users to start and run as version 2. 

MOSS workflows don't support versioning in a straightforward sense, where you can seamlessly deploy successive versions of a workflow.  But this doesn’t mean we’re powerless.  There are ways to simulate versioning.

 If you attempt to simply install version 2 over version 1, you’ll just create a headache for yourself and your users – don’t do it.  You may have the idea to version your workflow assemblies, and version your workflow InfoPath forms.  It’s an interesting idea, but it just doesn’t work properly.

Instead you’re going to have to deploy the subsequent version it as if it were a completely new workflow.  It gets its own GUID in the workflow.xml, and has to be associated with the lists and libraries all over again.  You’ll want to set the version 1 workflow to “No New Instances.”  To do this, go to the list’s settings screen and go to Workflow Settings -> Remove Workflows.  Here you’ll find a radio button to set the workflow to “No New Instances.”  When version 2 is deployed and associated, only version 2 will be allowed to start on an item.  At the same time, any currently running version 1 workflows will continue to operate normally.

This solution has weaknesses, but we can at least address them.

A major weakness is that the name of this workflow will have to be different.  It’s probably advisable to simply append a _v2 to the end of the original workflow name, so you now have MyWorkflow and MyWorkflow_v2 running on the list.  Even though the end users can’t run MyWorkflow anymore, you can imagine that any name change (even a simple one) can wreak havoc with a large non-technical user base.  This can present a training hurdle that may be unacceptable for larger organizations.

If you have the workflow start automatically this is less of a problem, since the users will not have to explicitly tell the system what workflow to start.  They don’t have to click on a particular name to use it.  However, many workflow implementations are started manually by the users, so we need to come up with something that will allow them to start new versions without having to see the new name.

A solution to this requires some foresight, and is best implemented before rolling the first version out to your user base.  The main idea is to come up with some alternate procedure that the users will use to start the workflow, something other than the out-of-the-box manual start-up; a ‘middleman’ application that the users interact with directly.  It will either start the workflow programmatically or direct the user to the Initiation form. 

Examples of this ‘middleman’ may be a webpart that includes a form that, when submitted, programmatically starts the workflow.  Or it may be a link that somehow directs the user to the initiation form.  Find further explanations of these ideas here: Start a MOSS workflow programmatically and here: Start a custom workflow from the context menu dropdown.

This ‘middleman’ application will need to be the standard way for end users to start the workflow, and they should be trained to use it.  This way, when a new workflow version is deployed, the technical team can also alter the ‘middleman’ app to point to the correct workflow.  The end users interacting with the ‘middleman’ will not have their experience altered, and the new workflow can be deployed seamlessly (at least in the eyes of the end-users).

A second weakness is that by default, a status column is added to the default view of the list or library for each workflow.  Since version 2 in this case is technically a completely new workflow, it will create a new status column.  This means that your default view will contain a status column called MyWorkflow and another called MyWorkflow_v2.  As you continue to add subsequent versions, you keep adding columns.

One thing you can do is simply remove the version 1 column from the view.  You’d probably want to wait until all instances are complete so your users can continue to monitor them as they’re in progress.  This is a very manual process, though, and if you have multiple lists running the workflow, this can be too much of an administrative burden.

Thankfully, you can write code to suppress the version 1 column from the view, checking first to see that all instances are complete.  You may choose to embed this code into version 2, so that it runs each time version 2 runs.  Or you may come up with a scheduled process that you can control better.

In all, workflow versioning is a feature that is missing from MOSS, but with some creativity and some custom coding we can develop solutions to work around this.  I’ll try to update this posting as I figure out more on this topic.

By: Daniel Herzog | Posted: December 26, 2006 at 10:03 PM

MOSS provides a couple of ways for you to designate how a workflow should start at association time.  You can have the users start it manually, or you can have it start automatically when a new item is created or updated.  You may find a need at some point to programmatically start a workflow, like from another application, or from some custom code running in MOSS.  Thankfully, it’s a fairly straightforward process.

 

To start a workflow, you’ll use the StartWorkflow() method. 

 

spSite.WorkflowManager.StartWorkflow(SPListItem spListItem, SPWorkflowAssociation associationTemplate, string initiationData);

 

As you can see, you’ll need a few parameters – the only tricky parameter is the initiation data XML string.  To build the string, it’s easiest to first create a class that has all of the properties needed for initiation, and then serialize that class.  The initiation data would mirror whatever data you are normally collecting in the workflow’s initiation form.

 

CREATE THE CLASS:

    [Serializable()]

    public class InitData

    {

        private string _field1 = default(string);

        public string Field1

        {

            get

            {

                return this._field1;

            }

            set

            {

                this._field1 = value;

            }

        }

        private string _field2 = default(string);

        public string Field2

        {

            get

            {

                return this._field2;

            }

            set

            {

                this._field2 = value;

            }

        }

     }

 

 

START WF CODE:

The example assumes you have an SPSite object spSite, an SPList object spList, and an SPListItem object spListItem.

First get the SPWorkflowAssociation object for the workflow on the current list:

const string WF_GUID_STRING = "DC904E02-BEBE-4581-AA4F-AE4E843D1111"//GUID for the workflow (from workflow.xml definition file)

 

SPWorkflowAssociation associationTemplate= spList.WorkflowAssociations.GetAssociationByBaseID(new Guid(WF_GUID_STRING));

 

Now we’ll define the initiationData parameter.  Note the private string method below.

string initiationData = getInitXmlString();

 

OK- we’re all set to start the workflow.

SPWorkflow myWorkflow = spSite.WorkflowManager.StartWorkflow(spListItem, associationTemplate, initiationData);

 

 

This method first creates and populates an InitData object with initiation data, and then serializes the object to an XML string that can be passed into the StartWorkflow method.

private string getInitXmlString ()

{

InitData data = new InitData();

      data.Field1 = <some value>;

      data.Field2 = <some value>;

      //etc…

 

      using (MemoryStream stream = new MemoryStream())

      {

           XmlSerializer serializer = new XmlSerializer(typeof(InitData));

           serializer.Serialize(stream, data);

           stream.Position = 0;

           byte[] bytes = new byte[stream.Length];

           stream.Read(bytes, 0, bytes.Length);

           return Encoding.UTF8.GetString(bytes);

      }

}

By: Daniel Herzog | Posted: December 26, 2006 at 12:02 AM

I was tasked with giving the users the ability to start a custom workflow on a document via the context menu dropdown in a document library.  I settled on a strategy that would add a link to the dropdown and be flexible enough to reuse for several workflows.

The basic steps are:

1.       Add a link to the context menu dropdown

2.       Create a custom ASPX page that will handle the redirection to the workflow initiation form

3.       Deploy your solution

The first thing to do is to add the link to the dropdown.  This link will call up the custom ASPX form that you will build in the next step.  Notice the tokens in the URL.  MOSS makes several tokens available, including the ~site, {ListId}, and {ItemId} used below.  The Template ID is the GUID that you used to deploy your workflow.

Create an XML file that looks something like this:

<?xml version="1.0" encoding="utf-8" ?>

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

  <CustomAction

  Id="DocLibMenu.ItemToolbarStartWorkflow"

  RegistrationType="List"

  RegistrationId="10000"

  Location="EditControlBlock"

  Sequence="106"

  Title="My Workflow">

    <UrlAction Url="~site/_layouts/StartWorkflow.aspx?ListID={ListId}&amp;ID={ItemId}&amp;WFTemplateID=dc904e02-bebe-4581-aa4f-ae4e843d1111"/>

  </CustomAction>

</Elements>

Now create your ASPX page, we’ll call it StartWorkflow.aspx.  The ASPX file itself can be pretty simple, just the standard MOSS headers and a string variable that you’ll define in the code behind. (something like <%=output %>).  Its really only used for messages to the user in case something goes wrong.

In the code behind, you’ll want to inherit from the MOSS LayoutsBasePage:

public class StartWorkflow : LayoutsPageBase

You’ll read the QueryString variables:

Guid listId = new Guid(Request.QueryString["ListID"]);

Guid wfTemplateId = new Guid(Request.QueryString["WFTemplateID"]);               

int itemID = int.Parse(Request.QueryString["ID"]);

 

Now get the List and Item objects for the item that you’re running the workflow against:

SPList theList = Web.Lists[listId];

      SPListItem theItem = theList.GetItemById(itemID);

Get the workflow association object:

SPWorkflowAssociation theWFAssociation = theList.WorkflowAssociations.GetAssociationByBaseID(wfTemplateId);

 

Check if the association is valid and enabled:

 

      if (theWFAssociation == null || theWFAssociation.Enabled == false)

      {

          isError = true;

          output = "The workflow will not start because workflows of this type are not currently enabled on the library";

      }

 

Now find if there are any running instances of the requested workflow.  You can only have one running instance of a workflow per list item.  This is a MOSS limitation that I’ll address in a later blog entry – unfortunately not with a solution.

 

if (theItem.Workflows.Count > 0)

{

foreach (SPWorkflow currentWF in theItem.Workflows)

{

if (currentWF.ParentAssociation.BaseTemplate.Id == wfTemplateId && currentWF.InternalState == SPWorkflowState.Running)

{

isError = true;

                        output = "The workflow will not start because a previous workflow of this type is still active and has yet to be completed.";

}

}

}

           

If there are no errors, then go ahead and redirect to the workflow initiation form.

 

if (!isError)

{

SourceURL = theList.DefaultViewUrl;

 

string InstantiationUrl = theWFAssociation.InstantiationUrl;

            TemplateID = theWFAssociation.Id;

 

            theURL = Web.Url + "/" + InstantiationUrl + "?List=" + listId.ToString() + "&ID=" + Request.QueryString["ID"] + "&TemplateID=" + TemplateID.ToString() + "&Source=" + SourceURL;

 

            Response.Redirect(theURL);

}

To deploy your solution, you’ll need to do a few things:

1.       Deploy the dropdown link as a feature.

2.       Strong-name and compile the ASPX page and deploy the DLL to the GAC.

3.       Put your ASPX file the LAYOUTS directory (C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS).

As I mentioned before, one nice thing about this solution is that the StartWorkflow.aspx form is pretty generic and can be reused for just about any workflow you create.  Just change the WorkflowId GUID value in the QueryString when you call the page up to the GUID you used to define the workflow when you deployed it.

 

 About Dan Herzog

Managing ConsultantDan Herzog is a managing consultant with more than six years experience designing and developing advanced software systems. He has done most of his work with collaboration technologies including Micro... [more]

 Tag Cloud

 External Links