0 Comments

During one of the service packs to SharePoint 2007 the ability to run anonymous workflows was turned off and to the best of my knowledge a good solution to the issue was never found (even for SharePoint 2010).  I tried solutions such as http://wssguestaccount.codeplex.com/ and other like this but had run into constant issues with the entire user session being run as an impersonated user.  Clients kept asking me for a solution and after searching around with no luck I decided it was time to write a little code.

Enter the SharePoint 2010 "Site Level" event receiver. While workflows require a logged in user, the SPItemEventReceiver is not quite as picky and they run anytime an item is added or updated anywhere on our site (SPWeb). So how does this help you might ask? Well the basic idea is if a new item is created or an item is modified, we can find the workflows associated with that item a launch them. Here is the code I use to do just that: 


private void _checkWorkflows(SPListItem li, Event​Type et)​
{
   SPSecurity.RunWithElevatedPrivileges(delegate()
   {
       SPUserToken token = null;​

       using (SPSite site = new SPSite(li.ParentList.ParentWeb.Site.ID))
       {
           using (SPWeb web = site.RootWeb)
           {
               try
               {
                   string usr = web.AllProperties.ContainsKey(AnonWorkflowReceiverSettings.AnonWorkflowReceiverSettingsRunAs) ? web.AllProperties[AnonWorkflowReceiverSettings.AnonWorkflowReceiverSettingsRunAs] as string : "SHAREPOINT\\System";
                   token = web.GetUserToken(usr);
               }
               catch
               {
                   token = site.SystemAccount.UserToken;
               }
           }
       }

       using (SPSite site = new SPSite(li.ParentList.ParentWeb.Site.ID, token))
       {

           using (SPWeb web = site.OpenWeb(li.ParentList.ParentWeb.ID))
           {
               SPList list = web.Lists[li.ParentList.ID];
               SPListItem listItem = list.Items.GetItemById(li.ID);
               foreach (SPWorkflowAssociation wf in list.WorkflowAssociations)
               {
                   if ((et == EventType.Added && wf.AutoStartCreate) || (et == EventType.Updated && wf.AutoStartChange))
                   {
                       foreach (SPWorkflow runningWF in listItem.Workflows)
                       {
                           if (runningWF.Author == -1) // Kill all running WF that are anonymous since they will fail anyway
                           {
                               SPWorkflowManager.CancelWorkflow(runningWF);
                           }
                       }

                       site.WorkflowManager.StartWorkflow(listItem, wf, string.Empty, SPWorkflowRunOptions.Synchronous); // Works Async too
                   }
               }

           }
       }
   });
}

private enum EventType
{
   Added,
   Updated
}
 


 

 

You might notice that we have to run the workflow as a specific user and to do that I store the user name in the root web's property bag. I do default to the system account if no value is in the property bag but I suggest no running your workflows this way. The rest is just setting up your event receiver and adding a way for you to assign the user to run the workflow as. To do that I suggest you check out Creating SharePoint 2010 Event Receivers in Visual Studio 2010 and I added a page to the _layouts directory and add a custom link to it in Site Settings using a Custom Action.

 

So far the solution is working great for anonymous forms and other items that we allow users to submit on public web sites. As always though, please test before using this code in production or drop me a line if you need any consulting help.

 

I hope this helps someone.

 

Cheers,

James

0 Comments

Recently while working with a client, a requirement for global navigation in their new SharePoint 2010 farm came up.  They have approximately 10 web applications with a minimum of 10 site collections per web application, so manually maintaining the navigation at each site collection was out of the question.  I therefore started to look at the usual suspects for global nav.  I went over the SiteMapDataSource, SharePoint 2010 Navigation Menu, and multiple other custom solutions with them.  Each one had its benefits and drawbacks but we had just about settled on a robust custom solution when an idea came to me while watching a St. Louis Cardinals baseball game (not important to the solution).  What about the Managed Metadata Service?  We were only going to have one service for the whole farm and pretty much as long as the farm is up, so is the service (i.e. not dependent on another web application).  So I started looking…

There is a label that could be used as the display name, a large text box for description that could be used for the URL and built in custom sorting and if needed could support multiple languages in the future.  Once I determined that, it was just a matter of some custom code and POC testing.

I started by setting up a Group, Term Set and the nested Terms that would make up my navigation.  Assigning each Term a “Default Label”, populating the “Description” if I want it to be a link or leaving it blank if it was just a container and unchecking “Available for Tagging” for Terms but keeping it checked for the Term Set (more on that later):

TermStore

 


Next enter Visual Studio:

 
The Microsoft.SharePoint.Taxonomy namespace / assembly has the classes we need to pull our Managed Metadata and is fairly easy to use.  We start by getting the Terms we are looking for from the service:

TaxonomySession session = new TaxonomySession(SPContext.Current.Site);

var termStore = session.DefaultSiteCollectionTermStore;
var tsc = termStore.GetTermSets("GlobalNavigation", 1033);

if (tsc.Count > 0)
{
     var tsGlobal = tsc[0];
     var tc = tsGlobal.GetAllTerms();
           
     var items = _buildMenu(tc, Guid.Empty,
                    tsGlobal.CustomSortOrder == null ? new List() : tsGlobal.CustomSortOrder.Split(':').ToList());

     _renderMenu(items);
}


 
Notice I did have to hardcode the name of my Term Set “GlobalNavigation” but everything else is pretty generic.  I am assuming 1 Term Set in the entire service named GlobalNavigation but you could also narrow by the Group first to make sure.  Also notice the strange parameter call to my _buildMenu function.  When custom sorting is used the items are not returned sorted and there is not a property on a Term with its sort order, but instead the parent container (TermSet in this case) has a property called CustomSortOrder that is a string of guids separated by semicolons.  Therefore I split the string into a list correctly sorted for use in the function.

 
To build the structure of my navigation I created a private class to maintain the information:

 

private class _menu
{
    public Term item {get; set;}
    public List<_menu> children { get; set; }
    public int order {get; set;}

    public _menu(Term i)
    {
        item = i;
        order = 0;
    }
}

 
Then it was just a matter of parsing the terms and putting them in the right order:

 

private List<_menu> _buildMenu(TermCollection terms, Guid parent, List sortOrder)
{
    var ret = new List<_menu>();

    IEnumerable childTerms = null;

    if (parent != Guid.Empty)
    {
        childTerms = from k in terms
               where k.Parent != null && k.Parent.Id == parent && !k.IsDeprecated
               orderby k.CustomSortOrder
               select k;
    }
    else
    {
        childTerms = from k in terms
               where k.Parent == null && !k.IsDeprecated
               orderby k.CustomSortOrder
               select k;
    }

    foreach (Term child in childTerms)
    {
        var newItem = new _menu(child);
        if (sortOrder != null && sortOrder.Count > 0)
            newItem.order = sortOrder.IndexOf(child.Id.ToString());

        //Find this items sub terms - Recursion Rocks!
        newItem.children = _buildMenu(terms, child.Id,
            newItem.item.CustomSortOrder == null ? new List() : newItem.item.CustomSortOrder.Split(':').ToList());

        ret.Add(newItem);
    }
   
    return (from r in ret
                orderby r.order, r.item.Name
                select r).ToList<_menu>();
}

 
Once you get the List of _menu items you just have to use your favorite menu rendering technique, apply a little CSS, add your new control to your master page solution (or hack with SPD) and you have a menu based on your Term Set:

 Menu
 


Advantages:

  • “Term store management” is available to the contributors in each site collection as long as they can get to site settings
  • The Term Store is available across the entire farm without being dependent on a web application
  • Seems very fast (so far)but add caching to your control just to be sure

Quirks / Disadvantages:

  • You must make the Term Set “Available for Tagging” so everyone can see it.  This might confuse people if they go to use the Terms in a normal way and see our Term Set.  However, since we unchecked the tagging option for each Term, they really can’t do anything with the Term Set.
  • To get the custom sorting to work properly, every time you add a new Term you must change the sort order, save it and then change it back to get the CustomSortOrder to populate correctly.
  • This does not security trim but that could be added.
  • Requires custom code

Summary:
While I just proved this out this week and have not implemented in production yet, I have tested on multiple web applications and site collections including anonymous and so far so good.  The client flipped over the maintainability and “fool proof” nature of this solution but I am still searching for holes.  If you have any questions or comments, especially any inherent flaw, please let me know.

As always, I am a consult available to help implement / customize the full solution.  However, if enough people find it useful I would be happy to create a Codeplex solution.  This information is for discussion and education purposes only and is in no way guaranteed.

 
Cheers,
James

0 Comments
As I have started migrating my old web sites over to SharePoint 2010 I have been evaluating customizations that I made for my MOSS 2007 sites. On many occasions I had used the dataview web part to do thing like opening external links in new windows and hiding headers that are not needed. In comes JQuery and the content viewer web part.
 
 
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">

$(document).ready(function(){
//Hide the header row
$(".ms-listviewtable tbody tr:nth-child(1)").hide();

//Open links in new window​
$(".ms-listviewtable tbody a").click(function(){
this.target = "_blank";
});
});
</script>
 
 
This was pretty easy because all the list view web parts on the page contains all external links but this could be pretty easily to check the links before opening them in an external window.

0 Comments
I just started messing around with list template migration from V3 to 2010 and found it to be way too easy but very cool.

The first thing I did was save a list as a template in V3 and download to my laptop.

I then renamed the file from *.stp to *.cab and extracted the file Manifest.xml

The first change to make to manifest is to change

<productversion>3</productversion> to

<productversion>4</productversion>

Simple enough.


The next change I made was for any lookup fields I had. Do a search in the file for your lookup fields. You will see an attribute on thethat looks like List=”{GUID}”. All you have to do is change the attribute to List="Lists/<List name>" where < List name> is the name of the lookup list. And that is it.

Create a new CAB file, add your manifest and rename the file to *.stp again.

Upload the template to the SharePoint 2010 templates gallery.

Make sure that all source lookup lists have been created and then create your new list from the template.

When I did this I even had it keep the data in the template and everything matched up perfectly.​​​

0 Comments
I just gotSharePoint2010 Server installed and had a few lessons learned.

First, I followed the directions at:
http://msdn.microsoft.com/en-us/library/ee554869(office.14).aspx

Everything installed perfectly but when Central Administration came up the page was blank. So... I uninstalled and reinstalled SharePoint (4 times) and then went to bed. After a good nights sleep I realized it had to be an IIS issue.

I completely removedIISfrom the box and just followed the directions from the above link and magically it all worked perfectly. It may have been that I was also running WSS 3.0 on the same OS or maybe IIS just got corrupted but now it works.​​