Search

rafelo

Advancing Productivity and Operational Efficiency through Cloud Services and Apps

Category

Feature Development

How to video: Creating a Basic SharePoint 2010 Feature using Visual Studio 2010

A couple of weeks ago I demoed an example of how to create a basic SharePoint 2010 “Change Site Title” feature using Visual Studio 2010. A video of the demo is now available on the SharePoint Tech Dives site at http://www.sptechdives.com/?p=185

digg_url = “https://blog.rafelo.com/2009/11/how-to-video-creating-basic-sharepoint.html”;digg_title = “How to video: Creating a Basic SharePoint 2010 Feature using Visual Studio 2010”;digg_bgcolor = “#FFFFFF”;digg_skin = “compact”;digg_url = undefined;digg_title = undefined;digg_bgcolor = undefined;digg_skin = undefined;

Announcing SharePoint Tech Dives, coming to Houston Texas on November 11th, 2009.

A little bit about SharePoint Tech Dives (http://www.sptechdives.com): Each meeting is an open forum for discussion and learning around specific topics chosen by the community. Technology experts provide structure and guidance around each of the topics selected, while attendees are encouraged to ask questions as well as share their views, experiences, and opinions. At the end of each meeting attendees will help determine the topic for the following meeting.

Next Event – Wednesday, November 11th from 6:30 pm to 8:30 pm.
Developing SharePoint Features, Feature Receivers, and Feature Staplers

During this session we will discuss best practices and provide guidance around SharePoint Feature development,  Feature Receivers, and Feature Staplers. The session will include a hands on lab with step by step instructions on how to create a simple Feature with a Feature Receiver and Stapler. We will also demo how to create a similar Feature for SharePoint 2010, using the upcoming version of Visual Studio.  Space is limited, so be sure to reserve your seat. We look forward to seeing you there.

digg_url = “https://blog.rafelo.com/2009/11/announcing-sharepoint-tech-dives-coming.html”;digg_title = “Announcing SharePoint Tech Dives, coming to Houston Texas on November 11th, 2009.”;digg_bgcolor = “#FFFFFF”;digg_skin = “compact”;digg_url = undefined;digg_title = undefined;digg_bgcolor = undefined;digg_skin = undefined;

HTTP Error 403 – Forbidden with Custom or Updated Features and Application Pages

This is a fairly common and non-descriptive error that continues to surface more and more as the SharePoint developer community continues to grow. Surprisingly, I’ve found very little information in the web regarding what I believe to the most common cause and solution (hence this blog post).

Many things can trigger this error, and as you might expect by the description the error is security/permissions related. I’ve seen it most often, when browsing the Site Features and Site Collection Feature pages… but it has been known to take down a whole site; although mostly (and luckily) in dev environments.

So why do I keep referring back to development? And more importantly, what is this “most common” cause/solution? Its mostly tied to “drag and drop”; when you drag and drop files, permissions usually go with them (albeit there are a few exceptions.) With development rarely taking place directly in the 12 hive features, or layouts folder; its shouldn’t be surprising to see developers (and administrators alike) deploying and often testing changes to features by dragging and dropping files from their Desktop, My Documents, or project folders. Naturally this approach doesn’t follow best practices, but I wont deny having made the mistake while testing a quick change to a feature or elements xml file in a development environment. All updates to production environments should be done via WSPs.

So how do you solve the problem? Finding the culprit shouldn’t be too difficult, think of any items that have been recently deployed/updated. Cant think of any, search for any recently updated files of folders. If you do think of or find any; check the permissions and make any required modifications; also consider redeploying them via a WSP.

SharePoint Site Enumeration Page: Get a list of all the sites in your SharePoint farm from Central Administration

Updated 8/25/2008: Updates to formatting in code blocks and removed recursive call that was causing duplication.

In my previous post “Iterating through SharePoint Web Applications, Site Collections, and Sites (Webs)” I provided a simple script to iterate through all web applications, sites and site collections. In this post, I will use much of that code to create an application page that returns a list of all the sites in the farm in XML. I will also create a feature to display a link to the application page from the Application Management section in Central Administration.

Click here to download the Solution Package (WSP)
Click here to download the source files

First I create a new file called EnumerateSites.aspx in c:\program files\common files\microsoft shared\web server extensions\12\template\admin. I will be accessing the page from the Central Administration site; I prefer to put such pages in the “admin” folder which is not accessible from regular SharePoint sites, as opposed to the “layouts” folder.

Since this file will be returning a list of all the sites in XML, we need to make sure the browser renders it properly. To do so we change the content type of the page to “application/xml”

Next,we create references to the necessary SharePoint and XML assemblies:

Since we are only displaying XML we wont need to reference a master page; so the rest is pretty much the same code from the previous post executing in the OnLoad event of the page. I use the XMLTextWriter class to build the XML document, and incorporate a number of “try catch” statements throughout the code to ensure that any errors that may be encountered are properly displayed in the XML. I’ve pasted all of the code for the application page (including the 2 previous excerpts) below:

    protected override void OnLoad(EventArgs e){
        // Get references to the farm and farm WebService object
        // the SPWebService object contains the SPWebApplications
        SPFarm thisFarm = SPFarm.Local;
        SPWebService service = thisFarm.Services.GetValue(“”);

        // Prepare the XML Writer
        XmlTextWriter xmlWriter = new XmlTextWriter(Response.OutputStream, Encoding.UTF8);

        //Start the XML Document
        xmlWriter.WriteStartDocument();

        //Create a Web Applications Element
        xmlWriter.WriteStartElement(“webapplications”);

        //**********************************************
        // NOTE: From here on everything is executed in
        //       “try catch” blocks. This is done to
        //       facilitate troubelshooting in case any
        //       errors surface. The error is caught and
        //       rendered in an xml “error” element.
        //       since the pages MIME type has been
        //       changed to XML, allowing the error
        //       to surface would render the xml document
        //       unreadable in IE.
        //***********************************************
        try
        {
            //Iterate through each web application
            foreach (SPWebApplication webApp in service.WebApplications)
            {
                //Create an XML Element for each web application
                //and include the name in the “name” attribute
                xmlWriter.WriteStartElement(“webapplication”);
                xmlWriter.WriteAttributeString(“name”, webApp.DisplayName);
                try
                {
                    //Create a sites element for the site collections
                    xmlWriter.WriteStartElement(“sites”);

                    //Iterate through each site collection
                    foreach (SPSite siteCollection in webApp.Sites)
                    {

                        //Create an XML Element for each site collection
                        //and include the url in the “url” attribute
                        xmlWriter.WriteStartElement(“site”);
                        xmlWriter.WriteAttributeString(“url”, siteCollection.Url);

                        //call the recursive method to get all the sites(webs)
                        GetWebs(siteCollection.AllWebs, xmlWriter);

                        //close the site element
                        xmlWriter.WriteEndElement();

                    }
                    //close the site collection element
                    xmlWriter.WriteEndElement();

                }
                catch (Exception siteError)
                {
                    //if an error occurs write the error message to
                    //an error element
                    xmlWriter.WriteElementString(“error”, siteError.Message);
                }
                //close the web application element
                xmlWriter.WriteEndElement();
            }
        }
        catch (Exception webAppError)
        {
            //if an error occurs write the error message to
            //an error element
            xmlWriter.WriteElementString(“error”, webAppError.Message);
        }

        // close the web applications element and document
        xmlWriter.WriteEndElement();
        xmlWriter.WriteEndDocument();
        xmlWriter.Close();
    }
    //*************************************************
    // This method is used recursively to display all
    // webs in a site collection. The Web Collection
    // from the site collection is passed in along with
    // the XML writer to continue writing the XML Document
    // where the calling method left off
    public void GetWebs(SPWebCollection allWebs, XmlTextWriter xmlWriter)
    {
        //create a webs element to contain all sites(webs)
        xmlWriter.WriteStartElement(“webs”);
        try
            {
            //iterate through each site(web)
            foreach (SPWeb web in allWebs)
            {
                if (web.Permissions.DoesUserHavePermissions(SPRights.FullMask));
                {
                    //Create an XML Element for each site(web)
                    //and include attributes for the url, title,
                    //and template information
                    xmlWriter.WriteStartElement(“web”);
                    xmlWriter.WriteAttributeString(“url”,web.Url);
                    xmlWriter.WriteAttributeString(“title”, web.Title);
                    xmlWriter.WriteAttributeString(“WebTemplateID”, web.WebTemplateId.ToString());
                    xmlWriter.WriteAttributeString(“WebTemplateName”, web.WebTemplate);

                    //close the site(web) element
                    xmlWriter.WriteEndElement();
                }
            }       
            }
        catch (Exception webError)
            {
                //if an error occurs write the error message to
                //an error element   
                xmlWriter.WriteElementString(“error”, webError.Message);
            }
        //close the webs element   
        xmlWriter.WriteEndElement();     
    }

With the page saved in the admin folder, you could just access it via the URL of your central administration site:
(i.e. http://centraladminsite/_admin/enumeratesites.aspx)

I personally prefer to build a Custom Action which adds a link to the page from Application Management in Central Administration. The steps to do this are relatively simple:

  1. Create a new folder for the feature and call it “EnumerateSites”. We will use the feature to add a link to the page from Central Administration.
  2. Open the folder and create a new empty file naming it FEATURE.xml
  3. Open the file and paste the following XML, replacing NEWGUID with a newly generated GUID.


    <Feature xmlns="
    http://schemas.microsoft.com/sharepoint/
    Id=”NEWGUID”
    Title=”Enumerate Sites in XML”
    Hidden=”FALSE”
    Scope=”Farm”
    ActivateOnDefault=”TRUE”
    Version=”12.0.0.0″>

  4. Save and close the file.
  5. Create another file, this time naming it “Elements.xml” and paste the following XML, replacing GROUPGUID and ACTIONGUID with newly generated GUIDs (use the same GUID in both occurrences of GROUPGUID):


    <Elements xmlns="http://schemas.microsoft.com/sharepoint/“>
    <CustomActionGroup Id="GROUPGUID
    Location=”Microsoft.SharePoint.Administration.ApplicationManagement
    Title=”Application Management Utilities” Sequence=”1010” />
    <CustomAction
    Id=”ACTIONGUID
    GroupId=”GROUPGUID
    Location=”Microsoft.SharePoint.Administration.ApplicationManagement
    Sequence=”10” Title=”Enumerate Sites in XML” Description=”“>

    <UrlAction Url="_admin/enumeratesites.aspx” />

  6. Save and close the file
  7. Package the EnumerateSites.aspx page and feature using your method of choice. Don’t have one? Checkout WSPBuilder by Carsten Keutmann
  8. Deploy your solution and you are done!

You can access the page from the Application Management section of Central Administration, you’ll see a new section titled Application Management Utilities with a link to Enumerate Sites in XML.

image

Click on the link to get an XML page enumerating all of the sites in your farm, it should look similar to the following

image

Click here to download the Solution Package (WSP)
Click here to download the source files

Custom SharePoint 404 Handler (File Not Found Handler)

I recently performed another upgrade from SharePoint Portal Server 2003 to MOSS, and while the upgrade was executed without any major problems(I’ve earned bragging rights); we experienced a large number of calls due to broken links from other sites, and links that where saved as shortcuts or browser “favorites”. Most of them were due to a very large information re-architecture.

Some of the more important links where accounted for and redirected to the proper pages by adding URL mappings to the web.config file, a surprisingly easy though not very common technique. Undoubtedly many files would be left out, as its rather difficult (never say never) to find out what links and shortcuts people have saved. Ultimately they wanted a custom 404 page that would display a page with a friendly message using the portal look and feel.

Solution Overview

This is fairly easy to accomplish as SharePoint does provide an SPWebApplication property (FileNotFoundPage) to specify a custom 404 page. Unfortunately the property is only accessible via the API, and the file must be an HTML page located in \\Program Files\Common Files\Microsoft Shared\web server extensions\12\LAYOUTS\Locale_ID

Of course it would be easier if we could set this property from Central Administration, and why stop there, why not allow an administrator to specify a page in the portal, or anywhere else, and of any type for that matter. So I developed a SharePoint solution containing the following:

  • An HTML page to specify as the FileNotFoundPage of the web application. This page redirects all requests to custom application page where its easier to handle the request.
  • A settings page from where custom settings on how to handle the request can be specified for each web application.
  • A SharePoint application page that handles the request based on the custom settings specified for the web applicaiton
  • A feature that creates a menu item in “Central Administration > Application Management” for the settings page

 

I’ve made the WSP and Visual Studio Project available for download: (WSP, Project)

Solution Detail

The HTML file is simply a copy of the sps404.htm file that SharePoint installs by default in “c:\program files\common files\microsoft shared\web server extensions\12\template\layouts\1033”, I’ve simply replaced the path it uses to redirect requests to with that of my custom 404 handler aspx page.

The Custom404Settings.aspx page allows the user to select a web application and specify a custom 404 page. The value specified is saved to a custom property of the web application that can later be referenced by the Custom404Handler.aspx page.

The following code is used to update the FileNotFoundPage property of the web application and create a new property “Custom404Path” to store the specified custom 404 path value.



SPFarm farm = SPFarm.Local;
SPWebService service = farm.Services.GetValue("");
string webAppPropertyKey = "Custom404Path";

Guid webAppGuid = new Guid(lstWebApplications.SelectedValue);
SPWebApplication webApp = service.WebApplications[webAppGuid];

//create a web application property to store the
//path to the custom 404 page
if (webApp.Properties.ContainsKey(webAppPropertyKey))
{
webApp.Properties[webAppPropertyKey] = txtCustom404Path.Text;
}
else
{
webApp.Properties.Add(webAppPropertyKey, txtCustom404Path.Text);
}
lblMessage.Text = "Changes saved";

//update the FileNotFound property of the web application
//this needs to be an html page in the LAYOUTS/1033
webApp.FileNotFoundPage = "Custom404Page.htm";

//update the web application
webApp.Update();

The Custom404Handler.aspx page checks the value of the Custom404Path property of the web application and redirects the browser to that page. If the custom 404 path specified returns as the path not found (“oldUrl” querystring parameter) the page does not redirect the user and serves the custom 404 message itself (this is to avoid going into an endless redirect loop).

The following code is used to check the custom 404 path of the web application and perform the redirect.


using (SPSite thisSite = new SPSite(SPContext.Current.Site.Url))
{
string custom404Path = "";
string pageNotFound = Request.QueryString["oldUrl"];
SPWebApplication webApp = this.Site.WebApplication;

if (webApp.Properties.ContainsKey(webAppPropertyKey))
{
custom404Path = (string)webApp.Properties[webAppPropertyKey];
}

if ((custom404Path != "") && !(custom404Path.ToLower().Contains("custom404handler.aspx")) && !(pageNotFound.ToLower().Contains(custom404Path.ToLower())))
{
Response.Redirect(custom404Path);
}

}

The feature.xml and elements.xml files add a custom menu item to “Central Administration > Application Management” from where the users can specify the custom 404 settings.

feature.xml:

elements.xml:

Updated 7/28/2008 : Updated Samples, Project Files and WSP to include recommended changes for handling anonymous requests.

How to Create Custom SharePoint Groups from Features

While developing SharePoint Features, you may want to create a Custom SharePoint Group as part of the feature. Some common scenarios might include:

  • features that create workflows requiring specific groups for approvers
  • features that create custom libraries or list which only specific users(groups) should be able to access
  • features with custom application pages that provide specific functionality based on group membership

The list goes on and on; and while you could let the site owners create these groups and assign the necessary permissions (for most of these scenarios); wouldn’t it be safer if this was done by you. Think of all the time you’ll save on calls where you would otherwise have to explain how to do this, or troubleshooting where they’ve gone wrong. True, they’ll still have to add the users to the groups; but if they cant even do that maybe their place shouldn’t be anywhere near SharePoint.

In this posting I hope to demonstrate just how to do this by developing a feature that creates a Custom Group when activated.

We’ll begin by creating a folder called “CustomGroup” in the SharePoint feature directory (ie. “c:\program files\common files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\FEATUES\CustomGroup”)

Next we’ll create the FEATURE.xml file, it should look as follows:


<Feature Id="c552a987-fc9e-40fc-b061-404159cc7c03"
Title="CustomGroup"
Description="This Feature Creates a Custom Group"
Version="12.0.0.0"
Hidden="FALSE"
Scope="Site"
DefaultResourceFile="core"
ReceiverAssembly="CustomGroupFeature, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=TBD"
ReceiverClass="CustomGroupFeature.FeatureReceiver"
xmlns="http://schemas.microsoft.com/sharepoint/">

Notice the ResourceAssembly and ReceiverClass attributes. These serve as references to the assembly that will handle the FeatureActivated, FeatureDeactivating, FeatureInstalled and FeatureUninstalling events. The PublicKeyToken of the ReceiverAssembly attribute has been set to TBD, we wont have this until we’ve created and compiled the class.

Next we’ll create the SPFeatureReceiver class referenced above. Your code should look as follows:


using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;

namespace CustomGroupFeature
{
class FeatureReceiver : SPFeatureReceiver
{
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
//get a reference to the site collection where the feature is activated
using (SPSite site = properties.Feature.Parent as SPSite)
{
//get a reference to the root site / web of the site collection
using (SPWeb web = site.RootWeb)
{
//get a reference to a Site Owner
//(there should always be at least 1; position 0 of the
//Administrators UserCollection)
//we'll make this user the owner of the custom group
SPMember siteOwner = web.SiteAdministrators[0];

//prepare the group name
string grpName = "Custom Group";

//check if group exists
bool grpExists = false;

//-----------------------------------------------------
//THIS CODE SHOULD BE MOVED TO ITS OWN HELPER FUNCTION
//OR UTILITIES CLASS. IN FACT MOST OF THIS CODE NEEDS
//TO BE REFACTORED
foreach (SPGroup group in web.SiteGroups)
if (group.Name.ToLower() == grpName.ToLower())
grpExists = true;
//------------------------------------------------------

//add the custom group to the site
//if it doesnt allready exist
if (!grpExists)
{
web.SiteGroups.Add(grpName, siteOwner, null,
"Custom Group that I created because I can!");
web.Update();

//get a reference to the group we just created
SPGroup customGroup = web.SiteGroups[grpName];

//add the group to the quicklaunch
web.Properties["vti_associategroups"] =
web.Properties["vti_associategroups"] + ";"
+ customGroup.ID.ToString();
web.Properties.Update();
}
}
}
}

//.....FeatureDeactivating, FeatureInstalled, and FeatureUninstalling methods
}

The code has enough comments to be self explanatory. Make sure you create your class under the right namespace, as this is referenced in the FEATURE.xml file. You’ll also need to sign the assembly (give it a strong name), go to http://msdn2.microsoft.com/en-us/library/ms247123(VS.80).aspx for step-by-step instructions on how to do this.

Compile the assembly and add it to the GAC. While in the GAC,right click the assembly, select properties and make a note of the Public Key Token. You’ll need to update it on the FEATURE.xml file.

Deploy your feature via the Installfeature command in STSADM or by packaging it and deploying it in a WSP file.

Since we’ve set the scope of the feature to “Site”; you’ll have to activate your feature from the “Site Collection Features” link on the Site Settings page. If you are on a sub-site/web you’ll need to click on the “Go to top level site settings” link first.

Create a free website or blog at WordPress.com.

Up ↑

%d bloggers like this: