Tag Archives: WSS

Hiding Menu Items and Site Settings in SharePoint using Features

I have used the example of hiding the "Theme" button in Site Settings (as for corporate intranets with strict branding, this is quite a comment request. I would like to show you though how to not only hide specific menus, but how to hide whole sections of the Site Settings menu and other SharePoint menus too!

This all hinges on the HideCustomAction element which you can use in Features.

There are actually some pretty good (MSDN) references:

HideCustomAction – element

Default Custom Action Locations & IDs

How to: Hide a Menu Item in the ECB from SharePoint List Items

John Holliday – SharePoint Custom Action Identifiers

And a cross post, for completion:

How to: Add Actions to the User Interface

So What about this HideCustomAction thing then?

Ahhh yes, got carried away with MSDN references …

First off, you will need a feature (scoped at the Site Level)

Feature File (Feature.xml, don’t forget to put your own GUID value in)

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

<Feature Id="GUID" 
    Title="Hide UI Custom Actions"

    Description="This example shows how you can hide menus inside Windows SharePoint Services."

    Version="1.0.0.0"

    Scope="Site"

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

  <ElementManifests>

    <ElementManifest Location="HideUICustomActions.xml" />

  </ElementManifests>

</Feature>

Elements File (HideUICustomActions.xml)

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

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

<HideCustomAction

Id="HideThemeLink"

GroupId="Customization"

HideActionId="Theme"

Location="Microsoft.SharePoint.SiteSettings"

</HideCustomAction>

</Elements>

That will give you a feature which will remove the "Theme" links from the Site Settings menu. Now, let me quickly walk you through what the element file is composed of (hopefully the Feature file should be familiar enough).

HideCustomAction – This element defines a new custom action which we want to hide.

Id – This is optional (but recommended), used to identify this specific "hide" action from others.

GroupId – This is the GroupId value for the Custom Action that you want to hide.

HideActionId – This is the Id value in the Custom Action that you want to hide.

Location – This is the location value from the Custom Action that you want to hide.

If you want to find custom actions (and therefore access all of these values) then simply open up the Features folder (..\12\Templates\Features) and do a search for the phrase "CustomAction" in the files. You should find all of the CustomActions that are provided out of the box, and subsequently the ability to hide them if you want to! Exactly the same thing applies to Site Settings, so if you wanted to hide (for example) the "Themes" menu then you can find the Custom Action in the standard "SiteSettings" feature.

Enjoy, and let me know how you get on!

[Code Update] SDK and Microsoft Press Both Wrong?? Custom Fields in the schema.xml

UPDATE
For those who missed my original article (SDK and Microsoft Press Both Wrong?? Custom Fields in the schema.xml) , you can find it here!
 
I finally got round to posting the code that goes with this article ..
 
Please find below the details on code for an SPFeatureReceiver class, that will automatically push down changes from your Content Type Features
(you will need to attach the code to your feature using the ReceiverAssembly and ReceiverClass attributes in your feature.
 
The code needs to be created as a Class in a Class Library, with Microsoft.SharePoint.dll added as a reference, and then added as a Using statement in the class itself.
 
Code Sample
 
EDIT – Thanks to a quick spot from Nick’s SharePoint blog, I have updated the code to match! Thanks Nick!
 

class

ContentTypeInstaller : SPFeatureReceiver

{

// CODE DESCRIPTION

// —————-

/*

* This is a feature handler which should be paired
* with a content type feature.
*
* We have identified a problem with Content Types,
* where the content type site columns are not pushed
* down to custom list definitions, until they are
* "modified" first.
*
* This feature will interrogate all of the associated
* xml files that are attached to the feature.
* Once found, it will "modify" each of the custom fields
* that are referenced.
*
* This should allow list definitions to use the content
* type columns, without declaring them in the schema.xml
*/

public override void FeatureActivated(SPFeatureReceiverProperties properties)

{

using(SPSite site = (SPSite)properties.Feature.Parent)

{
SPWeb web = site.OpenWeb(

"");

// loop through each of the elements in the feature

foreach (SPElementDefinition element in properties.Definition.GetElementDefinitions(CultureInfo.CurrentCulture))

{

#region

Loop through feature elements

 

// retrieve the xml content for the feature element

XmlNode content = element.XmlDefinition;

// only continue if the element is a content type definition

if (content.Name == "ContentType")

{

// grab a new Content Type object

string strCTypeID = content.Attributes["ID"].Value;

SPContentType cType = web.ContentTypes[

new SPContentTypeId(strCTypeID)];

#region

Get FieldRef Order from Content type

// grab the original order, we will need this later

string[] fieldOrder = new string[cType.FieldLinks.Count];

int x = 0;

foreach (SPFieldLink fieldlink in cType.FieldLinks)

{
fieldOrder[x] = fieldlink.Name;
x++;
}

#endregion
#region

Add new columns to the content type

// loop through each sub-node in the Content Type file

foreach (XmlNode node in content.ChildNodes)

{

#region

loop through sub nodes

// only continue for

// the FieldRefs collection

if (node.Name == "FieldRefs")

{

foreach (XmlNode fieldRef in node.ChildNodes)

{

#region

Loop through FieldRefs

// only apply for FieldRef tags

if (fieldRef.Name == "FieldRef")

{

// get the FieldID and use it to

// retrieve the SPField object

string fieldID = fieldRef.Attributes["ID"].Value;

//SPFieldLink fieldLink = cType.FieldLinks[new Guid(fieldID)];

SPField field = cType.Fields[

new Guid(fieldID)];

// first we need to remove the fieldref

cType.FieldLinks.Delete(

new Guid(fieldID));

// and save, pushing this change down

cType.Update(

true);

// NOTE – this will NOT delete any content

// in existing lists!

// now add the field back in again

cType.FieldLinks.Add(

new SPFieldLink(field));

// and call an update, pushing down all changes

cType.Update(

true);

// NOTE – this is what adds the column to those

// lists who don’t already have it.

}

#endregion

}
 
}

#endregion

}

#endregion
#region

Apply changes

// reset the field order

// it is possible that adding and

// removing fields would have

// affected this

cType.FieldLinks.Reorder(fieldOrder);

// force update of the content type,

// pushing down to children

cType.Update(

true);

#endregion

}

#endregion

}
}
}

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)

{
}

public override void FeatureInstalled(SPFeatureReceiverProperties properties)

{
}

public override void FeatureUninstalling(SPFeatureReceiverProperties properties)

{
}
}
Enjoy, and happy coding!

SDK and Microsoft Press Both Wrong?? Custom Fields in the schema.xml

Ok, so I think I’ve found something here. It relates specifically to Site Columns and Content Types, and how they can be applied to Custom List Definitions.

According to the latest release of the WSS 3.0 SDK (August 2008?) and one of the "bible" textbooks for WSS 3.0 development (Inside WSS 3.0 – Ted Pattison & Daniel Larson – Microsoft Press) there are a couple of questionable "best practice" methods that I wanted some insight on.

Personally, I think I’ve found quite a big oversight, which could be causing problems all over the shop.

The Big Issue (not the magazine!)
The question is around building a custom List Definition, in which you want to implement a custom Content Type (with custom Site Columns).

Now, both the SDK and the Inside WSS 3.0 book (along with many many other references I’ve seen, including people) state that if you have custom fields in your content type, then you have to add them to the Schema XML.

This has always struck me as very odd. I mean, you have to add all of that information already to your Site Columns (fields) feature .. right? So aren’t you just copy-pasting the same info?

Also, if you try to do this through the user interface, SharePoint will automatically add all of the columns from the content type when you add it to the list … so why can’t it do that through CAML too ??

Well, I wasn’t happy about this, and decided to try things a different way.

The Big Experiment (not the Discovery Channel)
I wanted to try and make a set of features that proved these statements wrong:

  • Site Column Feature (which declares a single, custom text field)
  • Content Type Feature (which declares a single Content Type, inheriting from Document, and includes my site column as a Field Reference)
  • Document Library Feature (declares a new custom document library definition. The only change is the Content Type and switching on "management of content types". The custom field is NOT added to the Fields section in the schema.xml)

TAKE 1 – As expected
I finished my CAML, installed my features and activated them in a completely new vanilla Site Collection.
My site column and content type both appeared on activation (as expected) and my new library definition popped up in the create menu (also as expected).

I then created my Document Library … and … <anti><climax>my columns did not appear in the library</climax></anti>.

This was, for the cynics among you, entirely expected. Microsoft themselves (through the holy scriptures of the SDK) have stated that this would happen.

But then things got interesting …

TAKE 2 – The interesting result
Ok I thought … what is going on behind the scenes? Somehow I need my Content Type to take preference over the schema…

I had a bit of a think (and probably scratched my chin a few times).. eventually I hit upon an idea (don’t ask me how .. one of those late night coffee moments!)

I reset my environment (brand new, clean site collection) and re-installed and re-activated my features.

BUT .. this time, BEFORE I created any libraries I went to the Content Type Gallery and looked at my Content Type.
I clicked on my custom field (in the content type itself) and changed one of the values (I actually changed it into a required field) and hit the OK Button (note – the "push changes down to child content types" WAS selected).

I then backed out of those screens, and created my Document Library… and … <surprise>my custom columns DID APPEAR!!</surprise>!

This was quite a shock .. I mean .. the Fields section in my custom document library schema is empty (apart from the fields that come out of the box).

I checked the library settings, tried uploading a file, and even checked the Document Information Panel in Word 2007. My custom field was there, with all the correct metadata.

Conclusion – (the important bit .. for those who bothered to scroll down)

My conclusion:

You do not have to declare custom fields in the schema.xml, as long as you manually update your content type before creating any libraries.

But that’s not practical … I have <insert huge number>’s of content types!
Well .. I hear ya.

I have just finished off a custom Feature Receiver which can be attached to any Content Type Feature as a Receiver Assembly / Receiver Class.

The code will (when the feature is activated) locate all of the Content Type elements in the feature, and do an initial "Update – push down changes" when the content types are first activated. (UPDATE code as been published to my Blog. See this article for details).

This is effectively the "magic bullet" I was looking for, and with this:

  • Custom Fields do not have to be declared in the schema.xml
  • Custom Fields can still be referenced in views, even though they are not referenced as Fields.
  • All column and meta data management can be developed in the Content Types and Site Columns

Comments / Discussions / <not>Flames</not> …

all are welcome 🙂

Some things to note about using “Templates”

I’ve been doing some testing, mainly prompted when I was questioned about Site Templates (I originally thought that Workflows built in SharePoint Designer would not be copied over in templates) and they proved me wrong! I have done some reasonably extensive testing, and have come to the following results:

Quick Summary

  • SharePoint Designer Workflows will ONLY be copied over in a SITE TEMPLATE WITH CONTENT (any other configuration fails)
  • Custom Workflows (including the “out of the box” ones) will be copied over in ALL TEMPLATES.
  • If you hide the “default” content type then it will re-appear after using a Template.
  • Version history is not preserved when using templates with content.

List Templates include the following items:

  • Custom Workflows
  • Adding more Content Types
  • Re-ordering the Content Types
  • Changing the default Content Types
  • Adding a custom Content Type
  • Versioning Settings
  • Content approval Settings

Site Templates include all of the above, but if you also select “Include Content” then you will also get “SharePoint Designer Workflows”!

If is important to note that if you hide one of the “out of the box” content types from your list then saving a List Template OR Site Template will not have any effect! The content type will be back and visible when your new list / site is provisioned!

I then did some more general “what if” scenarios:

 

Query

Answer

What happens if the custom Content Type in the Site Template does not exist in the target system?

It creates a LOCAL (i.e. list level) copy of the Content Type, and links it to the parent of the original content type.

This means that it works as it used to work, but the “central” copy of the content type no longer exists so maintenance has to be done manually and changes cannot be “pushed down”.

What happens if you delete a Custom Content type while it is in use normally?

Error – “The Content Type is in use”

If you save with content, does it preserve version history?

No.

All items copied over will be created as version 1.0.

All draft versions are removed.

 

It is also important to state the Disadvantages of using Templates and that is mainly regarding Site Templates, and it is 3 main things:

  • Performance. Using lots of site templates will degrade the performance of your system.
  • Maintability. Maintaining the structure going forward will be difficult, as you have no “schema” to modify. All you can do is
  • Limited to the User Interface. Mainly with reference to lists, changing the default fields, default content types, and modifying some of the “back end” properties is not possible through the user interface, and therefore cannot be achieved using “List Templates”.
  • Automation. There is still much more flexibility in terms of automation when using “definitions”. We can tie in event handlers and automation tools to do “almost anything”.

Correctly disposing SPWeb objects

I guess SharePoint development is a bit like riding a wave. First off you find you do almost everything wrong … after a while you pick up some of the “best practices” (like disposing SPWeb objects) and then you find that you are still doing things wrong. Well … thats the learning curve I’m afraid .. and it rounds down to where your SPWeb object came from, and when you should be disposing it.
There are several different ways of retrieving an SPWeb object.
Take the following 3 examples:
1) SPContext
SPWeb web = SPContext.Current.Web
2) OpenWeb
SPSite site = new Site(“https://myserver/”);
SPWeb web = site.OpenWeb(“”);
3) Site “RootWeb”
SPSite site = new Site(“https://myserver/”);
SPWeb web = site.RootWeb;
All 3 of them are valid SPWeb objects, but they are also all slightly different.
Lets start off with SPContext. This is basically a context object which represents the context of the current site that you are navigating. The SPContext.Current reflects where you currently are in the SharePoint site collection (so SPContext.Current.Web will return the SPWeb object that represents the site from which your code is executing … for example .. the site that your web part is sat in).
There are 2 major issues with using SPContext.
1) It is context sensitive. Yes .. I know thats like saying the sky is blue, but you do need to consider this if you are looking at generic code that might be accessed from multiple projects. Pulling out the “SPContext” properties is fine from a web part, but you don’t want the same code executing from a Console Application or a Workflow Custom Action.
2) SPContext is in use by all manor of controls and parts on the site and the page. If you dispose of the SPContext.Current.Web object then you will find that you get a nasty error message next time you try to access that object (forcing you to refresh the page). So if you’ve ever seen the error message below, check your disposal!

“Trying to use an SPWeb object that has been closed or disposed and is no longer valid”

Make sure you dispose of OpenWeb(“”) objects!

This is very important. When you are basically just creating your own SPWeb objects you will need to make sure that you dispose of your objects correctly. The best way of doing this is encompassing your web object in a “Using” statement

using(SPSite site = new Site(https://myserver/);
{

using(SPWeb web = site.OpenWeb(“”))
{
// work with web object
} // web object disposed

} // site object disposed

Do NOT Dispose SPContext.Current.Web or SPSite.RootWeb
If you write a using statement such as:

// This is bad! m’kay ?
using(SPWeb web = SPContext.Current.Web)
{
}

What you are actually doing is disposing the SPContext.Current.Web object .. not just your own “web” object. THIS IS BAD. You should actually just declare them with a single, normal line, such as:

SPWeb web = SPContext.Current.Web;

And exactly the same applies to SPSite.RootWeb properties too!

Tip – Apply Branding in WSS 3.0

Here’s a quick tip, cos I don’t have loads of time.
We’ve had lots of enquiries from our clients about Branding in WSS 3.0. If you are running MOSS 2007 then you have all of the nice “Look and Feel” settings when you go to Site Settings, allowing you to not only change the Master Page and Style Sheet, but also to push those changes down through the rest of the Portal.
Well … to put it bluntly, WSS 3.0 won’t let you do any of that.
So .. how about creating a Branding Feature?
You can write custom event handlers for Features (called Feature Receivers, using the SPFeatureReceiver class), so that you can have code that executes when a Feature is Installed, Activated, DeActivated or Uninstalled.
I won’t go into the details here, but there are some great articles about this on the web and MSDN.
So hows this for an idea?
Write yourself a Feature that applies Branding to WSS 3.0 sites (or any site for that matter!). You can make it set a new Master Page, change the Style Sheet, the Logo, set a custom “Site Theme” or even just inherit all those properties from the Parent site.
One great use (that we use quite regularly) is to create a Hidden Feature that just applies the Parent Site’s branding settings to the new site. You can then use Feature Stapling to make sure that ANY (staple to “GLOBAL”) site that gets created will be branded .. you don’t need to worry about the site definition!
As for the values used for the Branding, well you can pass these in as “Properties”. Every feature can have them, and they are specified as follows (in the Feature.xml file).
<?xml version=”1.0″ encoding=”utf-8″ ?>
<Feature xmlns=”https://schemas.microsoft.com/sharepoint/
…>
    <Properties>
      <Property Key=”NewMasterPage” Value=”/_layouts/CustomBranding/NewMasterPage.master”/>
    </Properties> 
</Feature>
So in that feature we have specified a Property called “NewMasterPage” (you can have as many of these as you like).
Then … to get that Property value out in your Feature Receiver you can use the following line of code:
string strMasterUrl = ((SPFeatureProperty)properties.Feature.Properties[“NewMasterPage”]).Value;
If the property doesn’t exist, or the value is blank then it will return an empty String (so you shouldn’t need to worry about null reference exceptions!)
Then, all you need to do is change the SPWeb property and call SPWeb.Update() to apply the changes! For example:
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
// apply branding
using (SPWeb web = (SPWeb)properties.Feature.Parent)
{
   web.MasterUrl = strMasterUrl;
   web.Update();
  
}
}
Hope this gives you some good starting points! You might want to extend this further; here are some interesting things you might do:
  • Add a Property Flag to reset all sub-site branding settings. This can then be activated at the top level to reset all branding Site Collection wide.

  • Add an “inherit from Parent” Property. Then you can use SPWeb.ParentWeb to get the parent SPWeb object (and all of it’s branding settings).

  • Store the “original” branding settings (before you overwrite them) in the SPWeb.Properties hashtable. Then, add an “DeActivating” event, and put all the original properties back again! (so when you deactivate the feature, it puts the branding back in place.. a great “rollback” option!)

Good luck .. and get Branding!!!
MKeeper

Understanding Content Type IDs

This is a topic that is confusing to many developers, and this post aims to clear up some of the issues around Content Type IDs, how they are structured and how you can extend them.

Content Types are re-usable definitions for meta-data that can be stored in Lists and Libraries in SharePoint. I won’t go into them in too much detail, you can find a reasonable amount of information about Content Types here and Content Type IDs here on MSDN.

Content Type IDs
As you should know, Content Types are based around inheritence (and if you didn’t know that, then you should have followed the links above more carefully :P). Each content type inherits from a parent content type (apart from System which is the root of all Content Types). A child content type will automatically have the same site columns that the parent has.
You can see this structure in the "out of the box" (ootb) Content Type IDs:

0x = System
0x01 = Item (inherits from System)
0x0101 = Document (inherits from Item)
0x0120 = Folder (inherits from Item)

Basically you have a {ParentID}{ChildID} structure.
This goes for all of the ootb Content Types (and you can find a list of them all in the WSS 3.0 SDK, downloadable free from Microsoft).

Custom Content Types
If you want to customise Content Types then you are looking at a slightly different ball game. For some reason (don’t ask me) you cannot use the same {ParentID}{ChildID} structure. Instead you have to use a {ParentID}{00}{GUID} structure.

So lets say you wanted to create a Custom Content Type that inherits from Document and call it "Demo Document". You would create the ID as follows:

0x010100763A775BFF7849a0A65FE1F57F56CC33 = "Demo Document"

Now lets say you want to inherit from "Demo Document" and create a further child, say "Demo Sales Document", you would create the following:

0x010100763A775BFF7849a0A65FE1F57F56CC3301 = "Demo Sales Document"
0x010100763A775BFF7849a0A65FE1F57F56CC3302 = "Demo Sales Document"

Note that here we already have a custom Content Type ID, so we can just go back to {ParentID}{ChildID} again.

So each "child"  of a custom content type simply adds a unique 2-digit  id on the end of the Content Type ID. If you wanted more child types you would add "02", "03" and so on.

And you can go one step further, if you wanted to create another "child" section, for example:

0x010100763A775BFF7849a0A65FE1F57F56CC3301 = "Demo Sales Document" (inherits from "Demo Document")
0x010100763A775BFF7849a0A65FE1F57F56CC330101 = "Demo Sales Order Form" (inherits from "Demo Sales Document")
0x010100763A775BFF7849a0A65FE1F57F56CC330102 = "Demo Sales Invoice Document" (inherits from "Demo Sales Document")

So from this you can create simple and effective "Parent – Child" relationships throughout your Content Types which allow you to manage your meta-data quickly and effectively.

In the above structure, if you want to add a custom column to all custom documents? You just modify "Demo Documents" and it will also update all of the child content types too!

Closing Thoughts …
One final thing to be careful of. If you are using CAML (Collaborative Application Markup Language) then you need to be aware of the following:
Modifications to a Content Type applied through a CAML based feature will NOT update child content types!!!!!
If you want to make sure that child content types are updated then you either have to make the change through Site Settings –> Content Type Gallery, or make your changes through managed code (such as C# using the SharePoint API).
Both of those give you the option of updating all child Content Types .. but CAML does NOT (unless I am mistaken, please let me know if I am! :))

Recent Entries »