Showing posts with label 2010. Show all posts
Showing posts with label 2010. Show all posts

Thursday, 18 September 2014

Programatically overriding SharePoint record locks using Records.BypassLocks() in c#

Records are good - they are immutable and cannot be changed by users.

Record Centres are also good - they provide a repository for records that users can submit their documents to, to be transformed into records.

Event receiver rules that automatically commit as records any document submitted to a list are also good, because as soon as content is placed in that list it becomes a records, and so immutable.

However...

What if something goes wrong?  Perhaps a users submits a document that should not be locked as a record ans should be removed, or perhaps the wrong meta data is attached to the record?

In these cases an admin has to temporarily suspend the records management features of SharePoint in order to rectify the fault.  This is a big deal since SharePoint admins have a different role to server admins, and so normally would not have the authority (or indeed the know-how) to do this, whereas the server admins would not be able to understand the records and content.  In my experience the only people who know both how the system works, and what it is supposed to do, are the developers!  And we do NOT want to do admin, because among other things they somehow get paid more than we do...

Furthermore, if you are automatically declaring documents as records when they enter the list, you will not be able to "undeclare" the record from the SharePoint front end without first disabling this feature - quite a risk if you end up having to do this a lot.

This is why I like to create my own tools to fill the gaps, and in this case we programmers can turn to a little known feature of the Object Model to help us:   Records.BypassLocks().

This neat little method, part of the  Microsoft.Office.RecordsManagement.RecordsRepository namespace, allows use to define a delegate function that be be run even on locked down records.  Wrap it in a security delegate and you have the makings of a class that can be used to provide users (preferably power users and admins) with tools to manage their records centre WITHOUT suspending record handling functions.

Remember - this allows you to override records handling, so only use this if you really need to.  However if you have found this blog and read this far, you probably really need to!

Here is a basic method that shows you how to use Records.BypassLocks() in a simple record cancelling scenario.  In this example I pass in URL to the offending record, open a security delegate (in order to ensure that the process has permission to perform this task), and then use BypassLocks to edit the meta-data on the document to change the name and title and set the Status to "CANCELLED".

Please not that I have present this a method inside a class with no constructor - I will leave that up to you (simple copy the methods into whatever class you are working in if this confuses you).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint.Utilities;
using Microsoft.Office.RecordsManagement.RecordsRepository;  //You are going to need to include the dll in teh references
using System.Web;

namespace BusinessLayer
{

   public void Cancelrecord(string documentURL)
   {
        // Pass in the document URL in a format such as:
        //string documentURL = "http://test_reocrds/records/wrong_file.pdf";  

        //Run this in a security delegate

        SPSecurity.RunWithElevatedPrivileges(delegate() {

                using (SPSite site = new SPSite(SPContext.Current.Web.Url))
                {
                    using (SPWeb web = site.OpenWeb())
                    {
                        web.AllowUnsafeUpdates = true;
                        try
                        {

                            SPListItem target = web.GetListItem(documentURL);

                            Records.BypassLocks(target, delegate(SPListItem item)
                            {         //Perform any action on the item. 
                                       CancelItem(item);
                            });

                        }
                        catch (Exception ex)  // it all throws up to here
                        {
                             throw ex;
                        }
                        finally
                        {

                            web.AllowUnsafeUpdates = false;

                        }
                    }
                }
            });
       }

        //Set item name to start with CANCELLED and set status to CANCELLED

        public void CancelItem(SPListItem item)
        {
            try
            {        

                // Update the name of the file

                item.File.CheckOut();
                item.TrySetValue("Name", String.Format("CANCELLED {0}", item.Name));
                item.TrySetValue("Title", String.Format("CANCELLED {0}", item.Title));
                item.TrySetValue("Status", "CANCELLED");  //

                item.Update();

                //This records library is set to use check in/out and versioning so it records any updates such as this

                item.File.CheckIn("CANCELLED", SPCheckinType.MinorCheckIn);

            }
            catch (Exception ex)
            {
                // Permissions failed.  Undo checkout
                item.File.UndoCheckOut();
                throw ex;
            }
        }
    }

}


 In my example above I use an Extensions class to add useful methods to SPListItem class to get or set values.  Here is the class:
using System;

using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
namespace BusinessLayer

{

    public static class Extensions

    {

        public static T TryGetValue(this SPListItem listItem, string fieldInternalName)
        {
            if (!String.IsNullOrEmpty(fieldInternalName) &&
                listItem != null &&
                listItem.Fields.ContainsField(fieldInternalName))
            {
                object untypedValue = listItem[fieldInternalName];
                if (untypedValue != null)
                {
                    var value = (T)untypedValue;

                    return value;
                }
            }

            return default(T);
        }

        public static bool TrySetValue(this SPListItem listItem, string fieldInternalName, T value)
        {
            try
            {
                if (!String.IsNullOrEmpty(fieldInternalName) &&
                    listItem != null &&
                    listItem.Fields.ContainsField(fieldInternalName))
                {
                    listItem[fieldInternalName] = value;
                }

                return true;
            }
            catch
            {
                throw;
            }
        }
    }
}








Wednesday, 20 July 2011

Sharepoint 2010 managed meta-data taxonomy field data using web services

Sharepoint 2010 includes a very useful new concept: managed meta-data. This allows you to build taxonomic hierarchies, and provide document routing based on the meta-data.

Unfortunately, it seems that using this field type using a web service call is extremely complicated, and hardly seems to be documented at all (this is so typical of Sharepoint stuff...).  Microsoft have given us a new web service class ("taxonomyclientservice") but there hardly any documentation on it all (http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.taxonomy.webservices.taxonomyclientservice.aspx).



Here is an example that I have managed to get working (after MUCH trial and error) that will hopefully help you to do the following:
  1. opens a list using the Lists web service, 
  2. iterates through the fields in the default content type for the list (you can remove this bit and just iterate through the fields in the list if you like),
  3. if it finds a "TaxonomyFieldType" field, it then gets the "Shared Service ID" (GUID of managed meta-data service) and Term Set ID (GUID of term set defined on this field) required to run the Taxonomy web service call
  4. sets up the CAML to run the Taxonomy web service call,
  5. retrieves the taxonomy data for that field in XML.  
 I do not try to analyse what the taxonomy XML means - that is for another day!

Note that all my web service proxies are called "ListService" and "Taxonomys".

//get list data from webservice
ListService.Lists listService = new ListService.Lists();
// Use your own list name
XmlNode list = listService.GetList("My List Name");

// Let's have a look at it
System.Diagnostics.Debug.Print(list.OuterXml);

// Get the content types for this list
//Get our defined content type (see previous post on how to do this - you need your own values here!)
string contentTypeGuid = GetListContentTypeGuid("My List Name", "{GUID}", "My Content Type");

// Now get content type definition 
XmlNode contentType = m_listService.GetListContentType(mylistinfo.m_listName, contentTypeGuid);

//Get the fields from the content type
XmlNodeList nodes = RunXPathQuery(contentType, "//sp:Field");

// Loop through the fields in the content type
foreach (XmlNode node in nodes)
{
 // This stuff just sets up some variables based on the content type and skips over hidden fields
 string Name = node.Attributes["Name"].Value;
 if (Name == "ContentType") continue;
 
 string ID = node.Attributes["ID"].Value;

 if (string.IsNullOrEmpty(ID) || string.IsNullOrEmpty(Name))
     continue;

 bool hidden = false;
 bool ReadOnly = false;
 try { hidden = Convert.ToBoolean(node.Attributes["Hidden"].Value); }
 catch { }
 try { ReadOnly = Convert.ToBoolean(node.Attributes["ReadOnly"].Value); }
 catch { }

 if (hidden || ReadOnly)
     continue;

 string ShowInFileDlg = "";
 try { ShowInFileDlg = node.Attributes["ShowInFileDlg"].Value; }
 catch { }
 if (ShowInFileDlg == "FALSE") continue;


 string StaticName = node.Attributes["StaticName"].Value;
 string DisplayName = node.Attributes["DisplayName"].Value;

 
 // Now we can check the "Type" attribute
 string FieldType = node.Attributes["Type"].Value;

 if (FieldType == "TaxonomyFieldType")
 {
     // WE HAVE A TAXONOMY FIELD!!!

     // Lets get the shared service ID and the termset ID from teh List schema
     string sharedServiceIds = "";
     string termSetId = ""; 
     // jump a few nodes to get the correct bit of the schema (included for clarity)
     XmlNode childNode =  node.ChildNodes[1];
     XmlNode termNodes = childNode.ChildNodes[0];

     //Loop through these nodes until we find the information we need
     foreach (XmlNode term in termNodes.ChildNodes)
     {
         System.Diagnostics.Debug.Print("term = " + term.ChildNodes[0].InnerText.ToString());
         if (term.ChildNodes.Count > 1)
         {
             System.Diagnostics.Debug.Print("value = " + term.ChildNodes[1].InnerText.ToString());
             if (term.ChildNodes[0].InnerText.ToString() == "SspId")
             {
                 // Get shared services ID from list
                 sharedServiceIds = term.ChildNodes[1].InnerText.ToString();
             }
             if (term.ChildNodes[0].InnerText.ToString() == "TermSetId")
             {
                 // Get Term Set ID from list
                 termSetId = term.ChildNodes[1].InnerText.ToString();
             }
         }
  
     }
     
  
     int lcid = System.Globalization.CultureInfo.CurrentUICulture.LCID;
     string serverTermSetTimeStampXml = "";


     string termStoreIds = "<termStoreIds><termStoreId>" + sharedServiceIds + "</termStoreId></termStoreIds>";
     string termSetIds = "<termSetIds><termSetId>" + termSetId + "</termSetId></termSetIds>";
     //Always set timestamp to this
     string oldtimestamp = "<timeStamps><timeStamp>633992461437070000</timeStamp></timeStamps>";
     //Always set version to 1
     string clientVersion = "<versions><version>1</version></versions>";
     string timeStamp = "";

     // Taxonomy web service!!
     Taxonomys.Taxonomywebservice taxonomy = new Taxonomys.Taxonomywebservice();
     taxonomy.Credentials = System.Net.CredentialCache.DefaultCredentials;
     string resultXML = taxonomy.GetTermSets(termStoreIds, termSetIds, lcid, oldtimestamp, clientVersion, out timeStamp);



     //Loop through the XML
     string termValue = "MHA";
     string termGUID = "";
     string parentID = "";
     XmlDocument termSetXML = new XmlDocument();
     termSetXML.LoadXml(resultXML);
     XmlNodeList terms = termSetXML.GetElementsByTagName("T");
     foreach (XmlNode term in terms)
     {
         string termName = term.FirstChild.FirstChild.Attributes["a32"].Value;
         termGUID = term.Attributes["a9"].Value;
         try
         {
             parentID = term.Attributes["a25"].Value;
         }
         catch (Exception)
         {
             parentID = "";
     
         }
  
         System.Diagnostics.Debug.Print("termName = " + termName + ", termGUID = " + termGUID + ", parentID = " + parentID);   
     }                          
     
 }
}


If you would like to know how to analyse the attribute codes returned in the XML, then take a look at this blog entry: http://blogs.solidq.com/sharepoint/Post.aspx?ID=37&title=SharePoint+2010%2C+Managed+Metadata%2C+TaxonomyClientService+in+depth

Thursday, 2 June 2011

Mark ContentType as default in Sharepoint Designer 2010

This is pretty simple stuff, but I just spent ten minutes looking at Sharepoint Designer trying to figure out where I clicked to change the default content type on a list!  I guess I'm just not used to the ribbon :)

Google didn't help (probably too easy a problem for it) so I thought I would right this up in case anyone else was suffering.

I've created a content type, here it is associated with a list in Sharepoint Designer:
But how do I make it default?  I finally spotted the option after clicking through endless options in SP designer - the trick is to highlight the content type (and not click on its name to see the content type editor!) and the look at the ribbon:


Hurray!  There it is.
Click on this and we have a new default:
I hope this helps some other Sharepoint 2010 newbie.