Why You Should Start Putting Everything in Features
If you're just getting into .wsp development with SharePoint these days you're in luck, you've managed to skip a couple years of pain when it comes to getting retractable functionality into SharePoint. With the WSS Extensions for Visual Studio a lot this stuff has never been easier.
It's relatively "easy" to get content INTO SharePoint, it seems like anyone with a copy of SharePoint Designer can embed files, lists, .aspx's in a WSS instance.
The REAL problem is getting your stuff out cleanly, and to allow yourself a managed upgrade when it comes time to maintain this content. Hopefully some plan that DOESN'T rely on you moving files around in SPD or visiting a series of web.config's and bin folders on all the Web Front End machines in the farm. This is the beauty of features and solutions and this walkthrough will show you how to create a simple feature that does the following:
- Copy a local file into the WSS instance.
- Add a link to the Site Actions menu.
- Modify the web.config programmatically on all machines in the farm.
Before You Start
If you get lost you can download a copy of a working solution here. Before you can open it though you'll need a copy of Visual Studio .NET 2005 and a copy of WSS Extensions for VS 2005 v1.1. Unfortunately there isn't support for VS 2008 yet word on the street has it that it'll be out in June of this year (2008).
What You Should Know about Solutions
Solutions (.wsp) are .cab files. You can take any .wsp rename it to a cab and open it up in WinRar or unpack it with WinZip (useful for debugging). Solutions pretty much all look the same. They all:
- Have a manifest.xml in the root. This lists all the Features, Web Parts and assemblies in the solution. The schema of valid tags for a solution can be found here.
- Underneath the root of the cab is usually a directory for each feature that is in the solution.
- Inside each feature folder is usually a file named feature.xml which describes a the title, description, a feature receiver if one exists, the scope of the feature (Web, Site, Web Application or Farm), the location of dependent files, and the a series of element manifests (elements.xml) which usually sit in their own folder underneath the feature folder.
- Element manifests (elements.xml) describes pretty much all the functionality that this feature will have. It's in these elements.xml that we can specify custom links, new list instances, content types, workflows etc... What can be described in an elements.xml (which is quite a bit) is listed here.
- Below is a sample solution which contains a Solution (FeatureDemo) which has a manifest.xml, one feature that has one elements.xml. The sample.txt is an example of a dependent file (like a .aspx) that we plan on deploying as part of this feature.
Walkthrough
- After the extensions have been installed, open up VS and create a New Project. Choose an Empty project, name it and click OK.
- Right click on your new project->Add->New Item, choose a Module and name it Install Content. Rename the Module.xml file that gets created to elements.xml (this isn't a requirement, but it's more in line with the typical anatomy of a solution). It's of note that whenever we add items to our project this way (using Right Click->Add) we're really setting up a new feature. Every folder that gets added by you adding WSS items to the project will for the most part end up equating to a Feature in our .wsp.
- The WSS Extensions v1.1 add a new window that you can view WSP anatomy with called the WSP View. You can get to it via View->Other Windows->WSP View. When you do this you can see the anatomy of your solution. It should now look something like:
- See the sample.txt? Right now that will get deployed to the root of our site as dictated by the elements.xml.
- Now we're going to add a link to Google.com on the site actions menu. In the Solution Explorer, open up elements.xml and add the following xml just below the <Elements> tag. <CustomActionIf you want to dig more into what you can do with custom link actions there's more details on custom action attributes here. Essentially you can add custom links almost anywhere in SharePoint, from Lists, to Items to Context Menus...it's quite extensive.
GroupId="SiteActions"
Location="Microsoft.SharePoint.StandardMenu"
Sequence="1000"
Title="Google Search"
ImageUrl="/_layouts/images/ActionsSettings.gif"
Description="A public search engine."
>
<UrlAction Url="http://google.com"/>
</CustomAction> - Now the feature receiver, in the Solution Explorer add a new class called FeatureReceiver.cs under the InstallContent folder.
- Add a reference to Windows SharePoint Services (or Microsoft.SharePoint.dll) and then copy/paste in the following code:
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
namespace FeatureDemo
{
public class FeatureReceiver : SPFeatureReceiver
{
//All the modifications we will be doing to the Web.Config when we activate/deactivate this feature.
private ModificationEntry[] entries =
{
//Ensure there's a connectionStrings section.
new ModificationEntry(
"connectionStrings"
,"configuration"
,"<connectionStrings/>"
,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
,true)
//Create the connectionstring.
,new ModificationEntry(
"add[@name='ConnectionString'][@connectionString='Data Source=serverName;Initial Catalog=DBName;User Id=UserId;Password=Pass']"
,"configuration/connectionStrings"
,"<add name='ConnectionString' connectionString='Data Source=serverName;Initial Catalog=DBName;User Id=UserId;Password=Pass'/>"
,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
,false)
};
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
//Get a reference to the web application and then remove entries for the blorum.
SPSite site = properties.Feature.Parent as SPSite;
SPWebApplication webApplication = site.WebApplication;
site.RootWeb.Title = "Set from activating code at " + DateTime.Now.ToString();
site.RootWeb.Update();
foreach (ModificationEntry entry in entries)
webApplication.WebConfigModifications.Add(CreateModification(entry));
webApplication.WebService.ApplyWebConfigModifications();
}
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
//Get a reference to the web application and then remove entries for the blorum.
SPSite site = properties.Feature.Parent as SPSite;
SPWebApplication webApplication = site.WebApplication;
site.RootWeb.Title = "Set from deactivating code at " + DateTime.Now.ToString();
site.RootWeb.Update();
foreach (ModificationEntry entry in entries)
{
//Some entries we create but do not remove
if (!entry.CreateOnly)
webApplication.WebConfigModifications.Remove(CreateModification(entry));
}
webApplication.WebService.ApplyWebConfigModifications();
}
/// <summary>
/// Event that fires after this feature is installed.
/// </summary>
public override void FeatureInstalled(SPFeatureReceiverProperties properties)
{
}
/// <summary>
/// Event taht fires before this feature is installing.
/// </summary>
public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
{
}
/// <summary>
/// Creates a SPWebModificaion object based on our parameters.
/// </summary>
private SPWebConfigModification CreateModification(ModificationEntry entry)
{
SPWebConfigModification modification = new SPWebConfigModification(entry.Name, entry.XPath);
modification.Owner = "OurApplicationName";
modification.Sequence = 0;
modification.Type = entry.ModificationType;
modification.Value = entry.Value;
return modification;
}
/// <summary>
/// Container to hold info about our modifications to the web.config.
/// </summary>
private struct ModificationEntry
{
public string Name; //Name of the node
public string XPath;//Where the node is located
public string Value;//The value of the attribute/node
public SPWebConfigModification.SPWebConfigModificationType ModificationType;
public bool CreateOnly;//Whether we should create and not remove the node when deactivating the feature.
public ModificationEntry(string name, string xPath, string value,
SPWebConfigModification.SPWebConfigModificationType modificationType, bool createOnly)
{
Name = name;
XPath = xPath;
Value = value;
ModificationType = modificationType;
CreateOnly = createOnly;
}
}
}
}
It's worth mentioning that this is an adaptation of a post by Ted Pattison, a SharePoint consultant and training. Lets talk about this code for a second. We essentially create a list of modifications that we're going to do to the web.config. We do these on FeatureActivated and clean up after ourselves on FeatureDeactivating. This forms the basis of cleaner SharePoint development. For demo purposes we also rename the site title to the current time stamp so that we know the code ran.
8. Now open up the feature.xml in the WSP View and change the Scope to "Site" and register our event receiver. Depending on what you named your project your feature.xml file should should look something like this:
<Feature
Id="beb48f0a-9c42-40b9-a4b5-9d01d23106a8"
Title="InstallContent"
Scope="Site"
Version="1.0.0.0"
Hidden="FALSE"
DefaultResourceFile="core"
xmlns="http://schemas.microsoft.com/sharepoint/"
ReceiverAssembly="FeatureDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9f4da00116c38ec5"
ReceiverClass="FeatureDemo.FeatureReceiver"
>
<ElementManifests>
<ElementManifest Location="InstallContent\elements.xml" />
<ElementFile Location="InstallContent\sample.txt" />
</ElementManifests>
</Feature>
9. We're almost there! Finally open up your project properties and under the Debug section set the URL to the SharePoint site you want to deploy to. Now press F5 and watch it go!
![]()
10. That's it. If you now visit the site you'll see that our feature was activated under Site Actions->Site Settings->Site Collection Features. You can deactivate if you want and all our modifications through this feature will be gracefully removed. You can see our link to Google in the Site Actions Menu, our changed website title telling us that the activation code ran, our sample.txt document has been deployed to the site root, AND if you dig up the web.config you'll see our added <ConnectionStrings/> section and a connection string that we set. These changes will also disappear if you deactivate the feature.
![]()
![]()
![]()
There's also now a FeatureDemo.wsp file in the \bin\Debug\ folder. We could take this and deploy it to another farm using the following commands.
stsadm -o deploysolution -name FeatureDemo.wsp -immediate -allowgacdeployment -url http://w2k3-tyler-virt:83">http://w2k3-tyler-virt:83
stsadm -o activatefeature -id {featureGuid} -url http://w2k3-tyler-virt:83
In doing so we've now deployed content that is easily retractable AND upgradable. I dream of a day where all SharePoint content is delivered with this level of thoughtfulness!
There's a good chance that some of those steps were confusing, and because of that you can download the source project here.
There's a lot available when it comes to SharePoint development these days. I think the biggest paradigm shift most ASP.NET developers are going through is realizing that we are now participating in a large shared platform with SharePoint. We are no longer writing stand alone apps where the ASP.NET developer is boss. With WSS/MOSS development a lot more consideration to Farm Administrators and other WSS Developers is in order when rolling out your content.
Take Care,
Tyler


14 comments:
Everyting thing works fine until I deactivate the feature, uninstall the solution, change something ilke the default Title for the FeatureDeactivating event, rebuild, hit F5. That's when I get error: The language-neutral solution package was not found.
Any ideas on how to fix this issue?
Surprisingly enough the only fix I've found for the "The language-neutral solution package was not found." error was to close Visual Studio and then open it again.
..I know it's a terrible workaround but it's the only one I've ran into so far that worked.
HTH,
Tyler
I created a project. Added a couple of features... Everything worked fine.
Then I closed the solution i Visual Studio. Moved the project to a new location in TFS. Opened the project again. Now WSP View shows Error loading Solution.xml....
Any Ideas? Seems pretty fragile this stuff...
While I definitely agree with you that it's fragile there's 2 things going for you.
1) Version 1.2 just got released last week which I hope will prove to be a little more robust.
2) When the generated solution or solution structure starts to misbehave you can simply regenerate it by deleting the old and revisiting the WSP view (it'll regen out the whole solution/feature/element structure).
Hope some of that helps.
My Best,
Tyler
How do you "configure" a feature receiver in VSeWSS 1.2?
the feature.xml is regenerated, and the ReceiverAssembly etc are lost if you enter them manually.
hi,
I am having trouble to design the spwebconfig format for membership provider key.
Can any one please guide me how to add the membership provider key ?
Thanks
Deactivating, undeploying and uninstalling the feature does not remove the sample.txt.
I have expected another behaviour...
Any ideas?
Thank you
Try this,
Custom sharepoint Features
http://sarangasl.blogspot.com/2009/09/in-this-article-im-going-to-describe.html
Hey mate,
Is it easy for you to update the solution for VS 2008 & VSE 1.3 ?
can you please update for vs2008?
I am trying to use your example to add a web.config connection string in a VseWss 1.3 (Mar CTP)site definition project for a MOSS 2007 site. I placed the feature receiver class under the site provisioning handler and set the Receiver assembly/class attributes in the corresponding feature.xml. Site def deploys and works fine, except the web.config is not modified. Do I need to something in the site provisioning OnActivated method which appears to override the feature events?
Thanx
FStumpp
Great article. The code for adding the connectionStrings section if it didn't exist was the piece I was missing. Your code is flexible and well documented. Great job!
Между прочим, лучший способ обезопасить кого-нибудь от слежки - купить Подавитель связи
Nice article.
I've put together a blog post with some of the more esoteric details of how Feature Receivers work. Thinks like what triggers each event and which account it will run under - not quite as obvious as it first sounds.
http://blog.pentalogic.net/2010/06/sharepoint-feature-receivers-events-details/
Post a Comment