Get the Search Service Application for a specific site (programmatically)


October 30, 2013 - 13:46, by Steven Van de Craen - 0 Comments

I needed to get a reference to the Microsoft.Office.Server.Search.Administration.SearchServiceApplication instance for a given Microsoft.SharePoint.SPSite instance.

Here’s how you can do this in C#. I created it as an Extension Method.

public static SearchServiceApplication GetSearchServiceApplication(this SPSite site)
{
    SearchServiceApplication result = null;

    if (site != null)
    {
        SearchServiceApplicationProxy proxy = (SearchServiceApplicationProxy)SearchServiceApplicationProxy.GetProxy(SPServiceContext.GetContext(site));

        if (proxy != null)
        {
            Guid ssaId = proxy.GetSearchServiceApplicationInfo().SearchServiceApplicationId;
            result = SearchService.Service.SearchApplications[ssaId] as SearchServiceApplication;
        }
    }

    return result;
}

And here’s the PowerShell counterpart. I’m no scripting guru so this might be improved here and there.

function GetSearchServiceApplication([Microsoft.SharePoint.SPSite] $site)
{
    if ($site -ne $null)
    {
        $proxy = [Microsoft.Office.Server.Search.Administration.SearchServiceApplicationProxy]::GetProxy([Microsoft.SharePoint.SPServiceContext]::GetContext($site));

        if ($proxy -ne $null)
        {
            [System.Guid] $ssaId = $proxy.GetSearchServiceApplicationInfo().SearchServiceApplicationId;
            $result = [Microsoft.Office.Server.Search.Administration.SearchService]::Service.SearchApplications | ? { $_.Id -eq $ssaId };
        }
    }

    return $result;
}

May it serve you well!


SharePoint 2010: Taxonomy issue and Event Receivers


February 2, 2012 - 11:59, by Steven Van de Craen - 0 Comments

Issue

Today I was troubleshooting a customer farm where Managed Metadata would remain empty in SharePoint, even though it was filled in correctly in the document’s Document Information Panel.

Digging through the internal XML structures of the DOCX and also the Content Type Schema and Field XML, I couldn’t find a reasonable explanation.

The library was configured with multiple Content Types, but the issue occurred only on some of them. It appeared that for those Content Types, the Managed Metadata field was Optional, not Required.

I tried reproducing that configuration in a new document library but there everything kept working, so the issue had to be with the existing library.

Further analysis, comparison and reflection showed that the problematic library was missing some Taxonomy-related Event Receivers.

List with Taxonomy Event Receivers

There appear to be four (4) Taxonomy Event Receivers:

  • TaxonomyItemSynchronousAddedEventReceiver (ItemAdding) and TaxonomyItemUpdatingEventReceiver (ItemUpdating) are added when a Managed Metadata field is added to the List

 

  • TaxonomyItemAddedAsyncEventReceiver (ItemAdded) and TaxonomyItemUpdatedEventReceiver (ItemUpdated) are added when “Metadata Publishing” is enabled in the List Settings

Metadata Publishing

 

The problematic library at the customer was lacking the first set of Event Receivers, which are responsible for syncing the hidden field.

ILSpy Taxonomy Event Receiver

Fix

I’ve written a one-off script (Console App) that loops all lists with a Managed Metadata field on all sites in the site collection and ensures the Taxonomy event receivers. That code was taken directly from TaxonomyField.AddEventReceiverIfNotExists.

using System; using System.IO; using System.Text; using System.Windows.Forms; using Microsoft.SharePoint; using Microsoft.SharePoint.Taxonomy; class Taxonomy_Fix { public static StringBuilder sb = new StringBuilder(); public static string nl = "\r\n"; public static string url = "http://intranet"; public static void FIXALL() { using (SPSite site = new SPSite(url)) { foreach (SPWeb web in site.AllWebs) { FixTaxonomyReceiverOnWebLists(web); web.Close(); } } File.AppendAllText(String.Format("c:\\{0:HH_mm_ss}.txt", DateTime.Now), sb.ToString()); MessageBox.Show(sb.ToString()); } private static void FixTaxonomyReceiverOnWebLists(SPWeb web) { for (int i = 0; i < web.Lists.Count; i++) { SPList list = web.Lists[i]; if (HasTaxonomyField(list)) { sb.AppendFormat("{0}{1} has Taxonomy Field{2}", url, list.RootFolder.ServerRelativeUrl, nl); EnsureTaxonomyHandlers(list); } } } private static bool HasTaxonomyField(SPList list) { bool result = false; foreach (SPField field in list.Fields) { if (field is TaxonomyField) { result = true; break; } } return result; } private static void EnsureTaxonomyHandlers(SPList list) { AddEventReceiverIfNotExists(list.EventReceivers, SPEventReceiverType.ItemAdding, typeof(TaxonomyField).Assembly.FullName, "Microsoft.SharePoint.Taxonomy.TaxonomyItemEventReceiver", "TaxonomyItemSynchronousAddedEventReceiver", SPEventReceiverSynchronization.Synchronous); AddEventReceiverIfNotExists(list.EventReceivers, SPEventReceiverType.ItemUpdating, typeof(TaxonomyField).Assembly.FullName, "Microsoft.SharePoint.Taxonomy.TaxonomyItemEventReceiver", "TaxonomyItemUpdatingEventReceiver", SPEventReceiverSynchronization.Synchronous); } private static void AddEventReceiverIfNotExists(SPEventReceiverDefinitionCollection eventReceiverCollection, SPEventReceiverType type, string assembly, string className, string receiverName, SPEventReceiverSynchronization sync) { if (eventReceiverCollection == null) { throw new ArgumentNullException("eventReceiverCollection"); } if (string.IsNullOrEmpty(assembly)) { throw new ArgumentNullException("Valid assembly name required"); } foreach (SPEventReceiverDefinition erd in eventReceiverCollection) { if (erd.Assembly == assembly && className == erd.Class && type == erd.Type && erd.Synchronization == sync) { sb.AppendFormat("\t>> no action required{0}", nl); return; } } SPEventReceiverDefinition erd2 = eventReceiverCollection.Add(); erd2.Name = receiverName; erd2.Type = type; erd2.Assembly = assembly; erd2.Class = className; erd2.Synchronization = sync; erd2.Update(); sb.AppendFormat("\t>> Added TaxonomyEvent Receiver{0}", nl); } }

Disclaimer: not responsible for this code. Run at own risk. Feel free to adapt or improve as desired or required.

Conclusion

I can’t really explain why only some Content Types were affected. I’m guessing the Optional/Required setting of the Managed Metadata field is involved somehow, but I didn’t really confirm that through testing.

Also not sure why the Event Receivers were missing in the first place. It could be because some “questionable” actions happened during the setup of the site, but it could as well be a bug in SharePoint 2010 RTM or later.


Custom Lookup Field Types: migration from 2007 to 2010


March 4, 2011 - 17:23, by Steven Van de Craen - 4 Comments

Custom Field Types are a rather advanced topic, but very powerful as well. They allow for real integration of custom components inside standard List and Library rendering. (See my other posts on Custom Field Types)

There are some things you can run into. Especially if you have custom Lookup Field Types like Cascading Lookup or Connected Lookup. Here’s what I’ve learned from a recent issue migration one from 2007 to 2010.

Custom Properties

Storing additional information in your Custom Field Types was always a real pain. The mechanism had serious flaws and it required some really dirty programming to work with. So instead I store my custom properties the same way SharePoint does. The involved API is internal but can be exposed through reflection.

I’m adding the this ‘reimplementation’ to every Custom Field Type of mine. When providing a null value to SetCustomProperty, the attribute is removed from the field XML.

1 #region Reimplementation of Get/Set Custom Property 2 public new void SetCustomProperty(string propertyName, object propertyValue) 3 { 4 Type type = typeof(SPField); 5 if (propertyValue != null) 6 { 7 MethodInfo set = type.GetMethod("SetFieldAttributeValue", BindingFlags.NonPublic | BindingFlags.Instance); 8 set.Invoke(this, new object[] { propertyName, propertyValue.ToString() }); 9 } 10 else 11 { 12 MethodInfo remove = type.GetMethod("RemoveFieldAttributeValue", BindingFlags.NonPublic | BindingFlags.Instance); 13 remove.Invoke(this, new object[] { propertyName }); 14 } 15 } 16 public new string GetCustomProperty(string propertyName) 17 { 18 Type type = typeof(SPField); 19 MethodInfo getField = type.GetMethod("GetFieldAttributeValue", BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(String) }, null); 20 object o = getField.Invoke(this, new object[] { propertyName }); 21 return o as String; 22 } 23 #endregion

Now there’s also no more need to predefine custom properties in the FLDTYPES_*.xml either. This applies to any type of Custom Field Type.

AllowMultipleValues

You might see in some cases that when you set the value of SPFieldLookup.AllowMultipleValues to true, your custom field is reverted to a standard Lookup field.

Multi Lookup Custom Field Type reverted to Lookup

If this is the case, you could override that Property and implement it accordingly.

1 private bool _allowMulti = false; 2 public override bool AllowMultipleValues 3 { 4 get 5 { 6 bool.TryParse(GetCustomProperty("AllowMultipleValues"), out _allowMulti); 7 return _allowMulti; 8 } 9 set 10 { 11 if (this.AllowMultipleValues != value) 12 { 13 _allowMulti = value; 14 SetCustomProperty("AllowMultipleValues", _allowMulti.ToString()); 15 if (value) 16 { 17 SetCustomProperty("Mult", "TRUE"); 18 SetCustomProperty("Sortable", "FALSE"); 19 } 20 else 21 { 22 SetCustomProperty("Mult", "FALSE"); 23 SetCustomProperty("Sortable", "TRUE"); 24 } 25 } 26 } 27 }

This applies to custom Multi Lookup Field Types.

Note: could not reproduce the issue with a new custom lookup field type in a clean SharePoint 2010 environment…

AllowBaseTypeRendering

We’ve seen this issue in a recent migration from SharePoint 2007 to SharePoint 2010 for a custom developed Connected Lookup Field (Single and Multi Valued). The field was ported from SharePoint 2007 with the following FLDTYPES_*.xml configuration:

FLDTYPES_MyCFT.xml

The AllowBaseTypeRendering attribute will default to the rendering of the Base Type (in this case Lookup) when the Custom Field Type cannot be determined.

This had the weird effect that these fields would not render or store *any* value selected in a Multi Valued Field of this type. This behaviour might only be occuring when there’s a custom RenderPattern defined for the Custom Field Type.

In either way we had to remove the attribute for SharePoint 2010, since we really required the RenderPattern. Problem solved for new fields, but there was still an issue for existing fields (existing SharePoint 2007 content migrated to SharePoint 2010).

For those existing fields, we wrote a script that would remove the attribute causing the problem:

1 SPList list = web.GetList(listUrl); 2 SPField field = list.Fields["MyMULTI"]; 3 MyField clf = field as MyField; 4 clf.SetCustomProperty("BaseRenderingType", null); 5 clf.Update();

This is where the custom SetCustomProperty came in handy for removing the BaseRenderingType attribute (added because the Custom Field Type had the AllowBaseTypeRendering attribute).

In our case we knew exactly where the fields were that had our Custom Field Type, but it might be more difficult to write a generic script to find all fields based on the Custom Field Type…

Note: could not reproduce the issue with a new custom lookup field type in a clean SharePoint 2010 environment…

Enforce unique values

You can force unique values on fields in SharePoint 2010, however is is not compatible when storing multiple values.

Enforce unique values

If this is the case your custom “edit field control” should disable the option to “Enforce unique values” via JavaScript.

 

Oh, and for those wondering: you still can’t do cross site collection lookups with a lookup field. Which makes me wonder how that works in regard to the Content Type Hub.


SharePoint 2010: User cannot be found after using stsadm migrateuser


September 7, 2010 - 20:18, by Steven Van de Craen - 0 Comments

I’m wrapping up a SharePoint 2007 to 2010 migration with custom development including programmatically copying files and folders, custom Event Receivers, Web Parts, etc.

Since the new setup uses a new AD domain, user accounts were mapped to new accounts and migrated using stsadm –o migrateuser.

After this we noticed errors like “User cannot be found”.

A bit of investigation showed that the ‘migrateuser’ operation didn’t update SPFile.Author or SPFile.ModifiedBy and querying those properties would throw the above exception.

A look at the ItemXml confirmed this:

<z:row xmlns:z="#RowsetSchema" ows_FileLeafRef="3;#sample.pdf" ows_Modified_x0020_By="OLDDOMAIN\olduser" ows_Created_x0020_By="OLDDOMAIN\olduser" ows_File_x0020_Type="pdf" ows_ID="3" ows_Created="2010-06-28 15:20:48" ows_Author="133;#New User" ows_Modified="2010-09-07 18:41:07" ows_Editor="1073741823;#System Account" ows_File_x0020_Size="3;#1035567" ... />

Workaround

I didn’t want to script an update to all files (because that would trigger Event Receivers) so I avoided querying those properties directly and instead created an extension method to get those values through the SPListItem:

SPFieldUserValue fuv = new SPFieldUserValue(item.Web, (String)item["Author"]);
return fuv.User;

Scripting an update

There were a lot of Event Receivers in the custom solution so I didn’t take this path, mainly because I noticed in SharePoint 2010 the SPListItem.SystemUpdate() method will also trigger Event Receivers. Here’s a blog post on it:

http://blogs.msdn.com/b/mjsabby/archive/2010/01/24/disabling-events-in-sharepoint-2007-and-sharepoint-2010.aspx 

This is different from SharePoint 2007 as far as I recall. Feel free to comment on this.

Other mentions

This blog post by Keith Richie mentions a solution for SPWeb.Author: http://blog.krichie.com/2008/09/12/resetting-the-author-on-a-sharepoint-site-or-wherefore-art-thou-author-redux/

 

Be sure to take this into consideration !


Add pages to meeting workspaces programmatically


March 10, 2010 - 20:13, by Steven Van de Craen - 0 Comments

You can add pages to meeting workspaces through the User Interface (browser) and then you get the options to manage them. It is also possible to add pages through code, but there’s one catch you need to be aware of and that is the InstanceId of the meeting. This ID is used in meeting workspaces to distinguish content per instance of a recurring event. So each meeting instance has its own set of documents and list items througout each List or Library on the site.

Consider the following code:

string url = "http://moss/meeting1";
string newPageUrl = String.Empty;
using (SPSite site = new SPSite(url))
{
    using (SPWeb web = site.OpenWeb())
    {
        SPMeeting meeting = SPMeeting.GetMeetingInformation(web);

        meeting.AddPage(String.Format("Page {0}", DateTime.Now.ToString("HHmmss")), meeting.InstanceId, out newPageUrl);
    }
}

By adding the page using the InstanceId of the current meeting workspace it will be added to a folder named after the InstanceId in the Pages library. This has the following effects (also applies to items in the other lists and libraries on the meeting workspace):

  • the page cannot be managed through the UI (sorting, removal, etc)
  • the page is visible only in a single meeting instance in case of recurring events

meeting workspace in SharePoint Designer

If you want to add a page regardless of instance you can provide a zero (0) to the instanceId parameter of the AddPage method and it will behave identical to pages added through UI.


Office 2007 document templates and Content Types in SharePoint – lessons learned


January 29, 2010 - 15:09, by Steven Van de Craen - 1 Comments

A while ago I stumbled upon a serious design limitation regarding Content Types and centralized document templates. What then followed was a series of testing, phone calls with Microsoft, finding alternative solutions and deep dive into Office Open XML.

Request from the customer

“We want to use MOSS 2007 to create a collaboration site per project for our 400+ projects. These collaboration sites all use the same Content Types and document templates. We want to centrally manage those document templates so that we don’t need to make the same change 400+ times.”

Approach

Due to sizing we architected a solution with a dozen of Site Collections that would each hold a collection of project sites. We ‘Feature’-ized our Content Types and Site Columns so that they could quickly be activated on all Site Collections and used by the child sites. Document templates would be stored in a central document library and we would link to them in the Content Types on the project sites.

First issue

Linking to document templates really doesn’t play well with the Document Information Panel (DIP). I have blogged about this here:

Centralizing Document Templates in a library- Document Information Panel shows incorrect properties

 

We proposed a solution where the document templates in the central library would be pushed to the Content Type resource folder on site level. The code to perform the push would have to connect to the Site Collections, copy the template to the resource folder (http://sitecollectionurl/_cts/contenttypename) and link template and Content Type together.

When a Site Content Type is associated with a List it will be a List Content Type inheriting from the Site Content Type and the document template will be copied to the List Content Type resource folder (http://sitecollectionurl/listurl/Forms/contenttypename).

Second issue

Did I tell you that the column values (metadata) have different values based on the project site ? So when a project site is created we automatically update the List Content Type Column default value with the values for that specific project site. Unfortunately this is not supported when working with Office 2007 file formats because they only react on changes to the Site Column.

Consider the following scenario:

1) Set up a document library with a Content Type that has a text column with a default value
2) Upload a new .doc or .docx as Content Type template
TEST 1) Create a new document:
        .DOC:  the DIP will contain the text column with the default value
        .DOCX: the DIP will contain the text column with the default value
3) In SharePoint, modify the default value of the text column
TEST 2) Create a new document:
        .DOC:  the DIP will contain the text column with the updated default value
        .DOCX: the DIP will contain the text column with the original default value

Microsoft confirmed that this is by design.

Third issue

When designing our document templates with Content Controls mapped to our SharePoint fields we didn’t know that internally in the DOCX file it uses a GUID for mapping the Content Control with the SharePoint Metadata XML. For fields (Site Columns) created in the UI or through API this is the SPWeb.ID of where they were created. For fields created declaratively through Features this is the SPList.ID of where their Content Type is associated to.

So some things to notice

  • Creating a single document template with Content Controls mapped to your declaratively added Fields cannot be used in two different Document Libraries because the Content Controls lose the connection with Field (because the ID of the List is different and not updated in the Content Control)
    • The solution here is to create your fields in the UI or through the API (this could be in a Feature Activating event)
  • Copying a document template across Site Collections means different Web ID’s so it also affects fields created in the UI or the API

Finally

In the end we wrote some wrapper classes for Office 2007 file formats using System.IO.Packaging that would manipulate our document templates once they were copied over to a different Site Collection. We also rewrote our Features to create our Fields through the API (SPWeb.Fields.AddFieldAsXml()).

  1. Remove the SharePoint Metadata XML so that association of the Content Type to a List it would be regenerated automagically
  2. Loop through every Content Control and find to which Field they were mapped using information in it’s XPath. Then we would update the GUID’s in the Content Control to match the SPWeb.ID

Next time I’ll definitely take these design limitations into account. Lessons learned I’d say !


Programmatically change the Toolbar on a List View Web Part


January 28, 2010 - 13:31, by Steven Van de Craen - 7 Comments

A refresher

The ListViewWebPart is used for displaying contents of a List or Document Library on both the default View pages (such as AllItems.aspx, etc) and also on other pages in the same web.

When a List View Web Part is added to a page (say the home page of that web) a hidden view (SPView) is created dedicated to that Web Part. When switching to another view in the Web Part Properties it really copies all the settings of the selected view into the dedicated hidden view. Changes made afterwards to the ‘selected view’ will not be pushed to the ‘dedicated view’.

Today’s question

How to programmatically modify the Toolbar settings for a ListViewWebPart ?

Answer

It all boils down to getting a reference to the hidden dedicated SPView and making modifications to it. There are three options that you can set the Toolbar to:

image

Full Toolbar

This option translates to “Standard” for the SPView.ToolbarType property.

Summary Toolbar

This option translates to “Freeform” for the SPView.ToolbarType property. Additionally you must specify a CAML string that is used for the rendering of the toolbar.

The default CAML string for a Links List looks like this:

<IfHasRights><RightsChoices><RightsGroup PermAddListItems=\"required\" /></RightsChoices><Then><HTML><![CDATA[ <table width=100% cellpadding=0 cellspacing=0 border=0 > <tr> <td colspan=\"2\" class=\"ms-partline\"><IMG SRC=\"/_layouts/images/blank.gif\" width=1 height=1 alt=\"\"></td> </tr> <tr> <td class=\"ms-addnew\" style=\"padding-bottom: 3px\"> <img src=\"/_layouts/images/rect.gif\" alt=\"\">&nbsp;<a class=\"ms-addnew\" ID=\"idHomePageNewLink\" href=\"]]></HTML><URL Cmd=\"New\" /><HTML><![CDATA[\" ONCLICK=\"javascript:NewItem(']]></HTML><URL Cmd=\"New\" /><HTML><![CDATA[', true);javascript:return false;\" target=\"_self\">]]></HTML><HTML>Add new link</HTML><HTML><![CDATA[</a> </td> </tr> <tr><td><IMG SRC=\"/_layouts/images/blank.gif\" width=1 height=5 alt=\"\"></td></tr> </table>]]></HTML></Then></IfHasRights>

No Toolbar

This option translates to “None” for the SPView.ToolbarType property.

The code

I assume you are familiar with getting an instance to the ListViewWebPart and then retrieving the SPView instance using either reflection on the private SPView member or through the public Web and View GUID properties.

Next you can change the Toolbar schema XML through reflection as follows:

SPView view = ...;

 

Type viewType = view.GetType();

 

XmlNode toolbarNode = viewType.InvokeMember("GetNodeFromXmlDom", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, view, new object[] { "Toolbar" }) as XmlNode;

 

toolbarNode.Attributes["Type"].Value = "Standard";

 

view.Update();


Deserialize + serialize an InfoPath Form loses the processing instructions


January 21, 2010 - 17:16, by Steven Van de Craen - 0 Comments

Using xsd.exe you can generate a Class from your form schema (xsd) and then deserialize a form to an instance of that class. This makes it a lot easier to interact with its data.

CS class based on XSD

The code for serialization and deserialization might look like this:

public static T Deserialize<T>(Stream s)
{
T result = default(T);
    XmlSerializer serializer = new XmlSerializer(typeof(T));
    using (XmlTextReader reader = new XmlTextReader(s))
    {
         result = (T)serializer.Deserialize(reader);
    }
    s.Close();
    return result;
}
public static T Deserialize<T>(string s)
{
    return Deserialize<T>(new MemoryStream(Encoding.UTF8.GetBytes(s)));
}
public static string Serialize<T>(T o)
{
string result = null;
    XmlSerializer serializer = new XmlSerializer(typeof(T));
    XmlWriterSettings settings = new XmlWriterSettings(); 
    settings.Indent = true;
    settings.Encoding = Encoding.UTF8;
    using (MemoryStream stream = new MemoryStream()) 
    {
         using (XmlWriter writer = XmlTextWriter.Create(stream, settings))
         {
             serializer.Serialize(writer, o);
         }
         stream.Flush(); 
         result = Encoding.UTF8.GetString(stream.ToArray());
    }
    return result; 
}

However you lose the original processing instructions at the top of the XML file. If you want to keep those either do custom serialization using an XmlWriter or do some kind of merge code with the original XML and the XML coming from serialization.

InfoPath XML Processing Instructions

Quite obvious if you think about it but I keep forgetting it :)


Thumbnails in SharePoint


November 12, 2009 - 11:58, by Steven Van de Craen - 0 Comments

Recently stumbled upon this old article: Microsoft Office Thumbnails in SharePoint.

Has anyone actually done anything like this ?


SharePoint 2007: Update system properties (Created, Created By, Modified, Modified By)


April 13, 2009 - 14:09, by Steven Van de Craen - 16 Comments

The system metadata can be changed to any value desired using the object model. I did notice a bug that the "Created By” (aka Author) field wouldn’t update unless also the “Modified By” (aka Editor) field was set (either to a new or it’s current value).

SPListItem item = ...;
item[“Created By”] = web.EnsureUser(“luisb”).ID;
item[“Modified By”] = item[“Modified By”];
item[“Created”] = DateTime.Now.AddYears(-3);
item[“Modified”] = DateTime.Parse(“01/01/2000”);
item.UpdateOverwriteVersion();

  • I could only get it to work using UpdateOverwriteVersion and not with SystemUpdate
  • You can use the internal name or ID of the field if working with a different language pack/installation
  • Works both on lists and libraries (not that I doubted this really)


 Next >>