Sue Hernandez's SharePoint Blog

SharePoint and Related Stuff

Monthly Archives: March 2011

Error occurred in deployment step ‘Activate Features’: Operation is not valid due to the current state of the object

SharePoint 2010 and Visual Studio 2010.

I added an Event Receiver to a new Visual Studio 2010 project.  Very simple.  It was of type EmailReceived but I don’t think that matters in this case.  I went to deploy the project and got the following error message:

“Error occurred in deployment step ‘Activate Features’: Operation is not valid due to the current state of the object”

Apparently reading up on many people’s posts this can be many things.  Turns out mine was because I did something so normal:  I renamed the namespace and the class name of the class.  Well apparently when I did that, I got out of sync with the automatically generated Elements.xml – which I couldnt’ find at first.

So pop open your “EventReceiver1” file in solution explorer – expand the files underneath it by pressing the + sign.  Double-click the Elements.xml file.  You will see something like this:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
   <Receivers ListTemplateId="101">
      <Receiver>
         <Name>
EventReceiver1EmailReceived</Name>
         <Type>EmailReceived</Type>
         <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
         <Class>YourProjectNameHere.EventReceiver1.EventReceiver1</Class>
         <SequenceNumber>10000</SequenceNumber>
      </Receiver>
   </Receivers>
</Elements>

My problem was the <Class> element.  I had renamed my namespace to MyNamespace and my project to MyProjectName.  So my class should have been MyNamespace.MyProjectName.  That was it.

Easy

Advertisements

SharePoint 2010 Remote Metadata – Client Object Model and WS

Today I was playing with an Add-In to have an attachment go from Microsoft Outlook straight to SharePoint into a Document Library, but they have to specify the Metadata, and one of the metadata fields is of type “Managed Metadata”.  So I had to figure out how to remotely get the choices for the metadata fields – i.e. the terms in the term set that apply only to that column.

So it turns out that you can get the SchemaXml property off of a field in a List and that will return a lot of information to you, like what some of the key ID’s are.  Below is an example where I’m getting key information to be able to dynamically display a control for the metadata field in question:

ClientContext spCtx = new ClientContext(webURL);
Web remoteWeb = spCtx.Web;

List docLib = remoteWeb.Lists.GetByTitle(listName);
spCtx.Load(docLib);
spCtx.Load(docLib.Fields);
spCtx.ExecuteQuery();
Control lastControl = null;
foreach (Field field in docLib.Fields)
{
 int top = 5;
 if (lastControl != null)
 {
  top = lastControl.Top + lastControl.Height + 5;
 }
 if (!field.Hidden && !field.ReadOnlyField)
 {
  if (field.InternalName != "FileLeafRef" &&
   field.InternalName != "ContentType" &&
   field.Title != "Title")
  {
   // Title will be same as File Leaf Ref (Name)
   Label newLbl = new Label();
   newLbl.AutoSize = false;
   newLbl.Width = 200;
   newLbl.Top = top;
   newLbl.Left = 5;
   newLbl.Text = field.Title;
   pnlControls.Controls.Add(newLbl);
   switch (field.TypeDisplayName)
   {
    case "Managed Metadata":
     string xml = field.SchemaXml;
     XmlDocument xDoc = new XmlDocument();
     xDoc.LoadXml(xml);
     string termSetID = string.Empty;
     string anchorID = string.Empty;
     string sspID = string.Empty;
     XmlNode termSetIDNode = xDoc.SelectSingleNode("//Property/Value[../Name = 'TermSetId']");
     if (termSetIDNode != null)
     {
      termSetID = termSetIDNode.InnerText;
     }
     XmlNode anchorIDNode = xDoc.SelectSingleNode("//Property/Value[../Name = 'AnchorId']");
     if (anchorIDNode != null)
     {
      anchorID = anchorIDNode.InnerText;
     }
     XmlNode sspIDNode = xDoc.SelectSingleNode("//Property/Value[../Name = 'SspId']");
     if (sspIDNode != null)
     {
      sspID = sspIDNode.InnerText;
     }
     ManagedMetadataField mmdFld = new ManagedMetadataField(webURL, sspID, termSetID, anchorID);
     mmdFld.Left = 210;
     mmdFld.Top = top;
     mmdFld.Tag = field.InternalName;
     lastControl = mmdFld;
     pnlControls.Controls.Add(mmdFld);
     break;
    default:
     TextBox slineOText = new TextBox();
     slineOText.Width = 264;
     slineOText.Tag = field.InternalName;
     slineOText.Left = 210;
     slineOText.Top = top;
     lastControl = slineOText;
     pnlControls.Controls.Add(slineOText);
     break;
   }
  }
 }
}

Then, in the user control for the Managed Metadata Field type, I call on the TaxonomyClientService web service to get the terms in the term set.  The code is below, however I don’t know what the significance of the <timestamp> piece is yet.  Also, I don’t know if those @a9’s and @a45’s and such actually change names depending on ??

List<Term> relevantTerms = new List<Term>();
if (!string.IsNullOrEmpty(termSetID))
{
 taxonomyWS.Taxonomywebservice ws = new taxonomyWS.Taxonomywebservice();
 ws.Url = webURL + "/_vti_bin/TaxonomyClientService.asmx";
 // TODO
 ws.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
 string oldtimestamp = "<timeStamp>633992461437070000</timeStamp>";
 string clientVersion = "<version>3</version>";
 string termStoreIds = "<termStoreId>" + sspID + "</termStoreId>";
 string termSetIds = "<termSetId>" + termSetID + "</termSetId>";
 int lcidEnglish = System.Globalization.CultureInfo.GetCultureInfo("en-US").LCID;
 string timeStamp = string.Empty;
 string termSet = ws.GetTermSets(termStoreIds, termSetIds, lcidEnglish, oldtimestamp, clientVersion, out timeStamp);
 XmlDocument xDoc = new XmlDocument();
 xDoc.LoadXml(termSet);
 XmlNodeList relevantTermNodes = null;
 if (!string.IsNullOrEmpty(anchorID) && anchorID != "00000000-0000-0000-0000-000000000000")
 {
  XmlNode anchorNode = xDoc.SelectSingleNode("//T[@a9 = '" + anchorID + "']");
  if (anchorNode != null)
  {
   relevantTermNodes = xDoc.SelectNodes("//T[contains(TMS/TM/@a45,'" + anchorID + "')]");
  }
 }
 
 if(relevantTermNodes == null)
 {
  relevantTermNodes = xDoc.SelectNodes("//T");
 }
 foreach (XmlNode node in relevantTermNodes)
 {
  string id = string.Empty;
  string name = string.Empty;
  string path = string.Empty;
  XmlNode idNode = node.SelectSingleNode("@a9");
  if (idNode != null)
  {
   id = idNode.InnerText;
  }
  XmlNode pathNode = node.SelectSingleNode("TMS/TM/@a40");
  if (pathNode != null)
  {
   path = pathNode.InnerText;
  }
  XmlNodeList nameNodes = node.SelectNodes("LS/TL/@a32");
  bool first = true;
  List<string> extraNames = new List<string>();
  foreach (XmlNode nameNode in nameNodes)
  {
   if (nameNode != null)
   {
    if (first)
    {
     name = nameNode.InnerText;
    }
    else
    {
     extraNames.Add(nameNode.InnerText);
    }
   }
   first = false;
  }
  if (!string.IsNullOrEmpty(id) &&
   !string.IsNullOrEmpty(name))
  {
   Term newTerm = new Term(id, name, path);
   if (extraNames.Count > 0)
   {
    newTerm.AlternateTerms = extraNames;
   }
   relevantTerms.Add(newTerm);
  }
 }
}
relevantTerms.Sort();
return relevantTerms;

Note the use here of a custom class called Term and the fact that you can have Alternate Terms.

Finally, when I go to return the final selected value, you have to get the WssID from the hidden list called TaxonomyHiddenList.

public string GetSelectedValue()
{
 if(!string.IsNullOrEmpty(txtTermValue.Text))
 {
  foreach (Term term in terms)
  {
   if (term.TermName.ToLower() == txtTermValue.Text.ToLower())
   {
    // Found the right term
    return GetWssIDByTermID(term.TermID) + ";#" + term.TermName + "|" + term.TermID;
   }
  }
 }
 return null;
}
private string GetWssIDByTermID(string termID)
{
 int result = -1;
 try
 {
  ClientContext spCtx = new ClientContext(webURL);
  Web web = spCtx.Web;
  List hiddenList = web.Lists.GetByTitle("TaxonomyHiddenList");
  CamlQuery query = new CamlQuery();
  query.ViewXml = "<View><Query><Where><Eq><FieldRef Name='IdForTerm' /><Value Type='Text'>" + termID + "</Value></Eq></Where></Query></View>";
  ListItemCollection items = hiddenList.GetItems(query);
  spCtx.ExecuteQuery();
  if (items != null)
  {
   spCtx.Load(items);
   spCtx.ExecuteQuery();
   if (items.Count > 0)
   {
    result = Convert.ToInt32(items[0]["ID"]);
   }
  }
 }
 catch
 {
  result = -1;
 }
 return result.ToString();
}

SharePoint Designer 2010 Variable Number of Approvers

I have started my first foray into really getting into SharePoint Designer 2010’s “Start Approval Process” action. 

What I needed to do was to say that X number of people listed in one list had to at any given time approve everything in another list.  Well that really can’t be totally done, it seems, using OOB SharePoint Designer, and that was my requirement – ONLY SharePoint Designer, no custom, no custom SPD Actions.

What I figured I’d do was to have an “upper limit” on the number of people who can approve – say 20.  So I had a custom list of approvers with items numbered 1 thru 20.  If the “Responsible Party” column had a person in it, it should set a task for them, but if that column was blank, it should skip it.

So I figured I’d use the “Start Approval Process” action, because of 2 big reasons – (1) I needed the tasks to run in parallel; and (2) I needed the flexibility of being able to change the emails going out.   I tried the following approaches, all which did not work:

  • One “Start Approval Process” action, with all 20 rows being referenced, figuring that if it was blank, they would skip it (nope)
    • Tried it with returning the user as Login Names
    • Tried it with returning the user as String
  • Several parallel “Start Approval Process” actions, all wrapped around an IF block to say if the field was blank, don’t run it.
    • Tried it with returning the user as Login Names
    • Tried it with returning the user as String
  • Tried hard-coding all of the “Blank” people to the system account.  Then tried the IF blocks around all actions to say if the field was a system account, don’t use it.  (Never matched)

What I finally came up with was a round-about type of procedure.

  1. Create a variable called AllApprovers
  2. FOR EACH one of the 20, did this:
    1. IF Line # x Responsible Party is not blank THEN
      1. IF variable AllApprovers did not contain Line # x Responsible Party (returned as semicolon-delimited Login names)
        1. IF variable AllApprovers was blank
          1. Set variable ApproversVariable to Line # x Responsible Party
        2. ELSE
          1. Set variable ApproversVariable equal to ApproversVariable + “;” + Line # x Responsible Party
  3. IF the variable ApproversVariable is not empty
    1. Start Approval Process and set the Approvers list to the variable AllApprovers
  4. ELSE
    1. Set the workflow to Canceled and stop the workflow.

 

Basically what seemed to be happening is that when the workflow tried to serialize, it tried to convert the Responsible Party into a person.  I kept getting “Coersion” errors.  It seems that if you set the approver list to a variable, it doesn’t try to resolve it till it actually gets to it.

– Sue –