Just Released – MSDN: Securing Application Pages in Windows SharePoint Services 3.0. By me :)
Posted: June 3, 2009 Filed under: Application Pages, Best Practices, Development, MOSS, Security, SharePoint, WSS Leave a comment »An article I recently wrote has just been published on MSDN. It covers the basic principles of securing application pages, and why they are often at risk; as well as providing code samples on how to properly secure your application pages, including:
- How and when to validate page requests
- How to verify Base Permissions
- How to verify Role Definitions
- How to verify Group Memberships
Check it out at: Securing Application Pages in Windows SharePoint Services 3.0 (http://msdn.microsoft.com/en-us/library/dd878359.aspx)
Submitting Items to Lists from Custom Application Pages
Posted: May 6, 2009 Filed under: Application Pages, Best Practices, CustomActions, Design, Features, SharePoint, WSS Leave a comment »This posting is in response to a comment I had on a previous posting; “Using the InputFormSection and InputFormControl to build Custom Application Pages that look like OOTB Pages”. One of the readers posted a comment asking how to go about using the submit button to post to a list. Needless to say, I didn’t feel that answering his question by replying to his comment would be the best response. So I’ve decided to post my response here, where its a little bit easier to elaborate on the subject.
Application pages can in fact be used to create list items, but there are a number of things to consider. The first and probably most important thing, is that application pages can be accessed from any site in your SharePoint farm. If the list the form is meant to write to is accessible globally this may not be much of a problem, but I’d have to question how that list was created and where it is stored. Application pages are part of what I consider to be a fairly modular development framework in SharePoint, and the scenario mentioned doesn’t sound very modular at all; a page that is deployed or accessible from every site, but the list it writes to is only deployed to one. What if the site containing the list is deleted? Or certain fields changed? The site administrator would likely be able to do either, but he/she wouldn’t be able to update the Application Page, which would likely break.
So how do I recommend using Application Pages to write to lists? There are many ways, most (if not all) of which would involve Features. The following is a sample approach:
1. Develop a site or site collection Feature that creates a list. Since we will be writing to this list from a Custom Application Page, I recommend that the list be hidden. This recommendation stems from the fact that we don’t want anybody accidentally deleting or modifying the list, which may impact the Application Page. Of course this would probably mean creating another page to view and/or edit list items; this could be a Content Page or Application Page.
2. Develop the Application Page that writes to the list created by the feature, including logic in it that checks if the feature is activated. If the logic determines that the feature is not activated, the page should display a message stating that the feature needs to be activated for the page to work.
3. Use Custom Actions and Custom Action Groups to create a link to the Application Page(s) from the “Site Settings”, or “Site Actions” menu.
I don’t mean to imply that an Application Page shouldn’t be used to save items to a single globally accessible list. But it requires even more consideration and planning than the scenario mentioned above. Actually, I’ve had to develop such pages; in one example the list resides in Central Administration and is created as part of a Feature scoped at the Farm level.
In another scenario, the list may not be hidden, or even created by the feature. The list may be manually created (to capture very specific criteria) by a site or farm administrator, in which case another Application Page may be used to capture the location of the list, and save it in the property bag of the site or site collection. The Application Page that writes to the list, fist looks at the property bag to determine its location, if the property has not been set, the page simply returns a friendly error.
Like the above mentioned scenarios, I’m sure there are hundreds more. The point is all of them require some thought. Unless strictly used to display information that you know is always available, an Application Page is rarely a solution as a whole. Take time to carefully plan and design your Application Pages, examine your solution from multiple angles making sure it doesn’t easily break. Remember, if you fail to plan you are planning to fail.
Easily determine your sites Content Database with a Custom Application Page
Posted: March 19, 2009 Filed under: Application Pages, ContentDatabases, Development, SharePoint 3 Comments »During last nights presentation at the Houston’s SharePoint Users Group I demonstrated how to create a simple application page to determine the Content Database of any SharePoint site. While I have posted a link to the presentation in my previous post, I think the Application Page may be very useful to many and have decided to post it here to make it easier to find.
The application page looks as follows and is easily accessible on any site by appending “/_layouts/ShowContentDatabase.aspx” to the URL of the SharePoint site:
Creating the Application Page is very simple.
- Go to your LAYOUTS folder, usually located in “C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS”
- Create a new file named “ShowContentDatabase.aspx”
- Add the following code the your file and save your changes.
<%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Page Language="C#" MasterPageFile="~/_layouts/application.master"
Inherits="Microsoft.SharePoint.WebControls.LayoutsPageBase" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<script runat="server">
protected override void OnLoad(EventArgs e)
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
lblContentDatabase.Text = SPContext.Current.Site.ContentDatabase.Name;
});
}
<asp:content id="PageTitle" runat="server" contentplaceholderid="PlaceHolderPageTitle">
Content Database
<asp:content id="PageTitleInTitleArea" runat="server" contentplaceholderid="PlaceHolderPageTitleInTitleArea">
Content Database
<asp:content id="Main" runat="server" contentplaceholderid="PlaceHolderMain">
Content Database: <asp:Label ID="lblContentDatabase" runat="server" />
That’s it. The page should be saved to the LAYOUTS folder in each of your Web Front End (WFE) servers.. I would recommend packaging the page in a WSP file and deploying it that way if you have the time.
Custom SharePoint Application Pages and the Menu System
Posted: March 19, 2009 Filed under: Application Pages, CustomActions, SharePoint Leave a comment »Last night in Houston’s SharePoint Users Group (HSPUG) I gave a short presentation / introduction on Custom Application Pages and how to add items to the SharePoint menu system. During the presentation we discussed what application pages are, when to use them, and demonstrated how to create a simple application page and add it to the site settings menu. All of the information is contained in the presentation which can be downloaded here.
Apply Site Features Globally from Central Administration via a custom Application Page.
Posted: October 30, 2008 Filed under: Application Pages, CustomActions, Development, MOSS, SharePoint, WSS Leave a comment »This posting covers a subset of a more general subject; Using Custom Application Pages to Extend the Capabilities of the Central Administration Site. Perhaps I will elaborate on the subject another time. For now, I will cover how to create a custom application page to activate or deactivate site-scoped (web) features globally.
Not much of an explanation is needed in regards to the purpose or practicality of this posting, as I’m sure most of you have been there, but for the sake of being thorough here is a brief description of a problem it helps address:
With just about every SharePoint project I’ve worked on, there has been some level of custom branding, usually involving new themes, master pages, and page layouts. Ultimately, and preferably, these get deployed via site-scoped (web) features. While these features can be automatically activated during the site provisioning process for new sites via Feature Stapling, there are often existing sites that we need to activate the feature on. Activation of these features can generally be narrowed down to specific types of sites, that is sites created from specific templates or site definitions, and generally target specific web applications. Out of the box, SharePoint only allows activation of site scoped features per site via the Site Settings page or stsadmin.exe. Global activation of such features targeting sites of a specific type/definition requires the creation of custom scripts or batch files. The solution; create a custom application page for the Central Administration site that allows farm administrators to activate site features across all sites in a given web application and of a specific site definition.
WARNING: Great care should be taken when applying Features globally. A poorly developed feature, or one that is targeted towards one type of site and applied to another, may render the site inoperable. Make sure you test your features thoroughly with each applicable site definition/template before activating features globally using this or any other technique.
The first step is to create a page that inherits from the GlobalAdminPageBase class, which is in the Microsoft.SharePoint.ApplicationPages.Administration assembly. This is the class that most of your Central Administration Pages should inherit from as it implements security and other related logic with the Central Administration site.
Additionally, you’ll need to include references to the following Namespaces and SharePoint Controls as they will be used throughout the page:
<%@ Register Tagprefix="SharePoint"
Namespace=”Microsoft.SharePoint.WebControls” Assembly=”Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c” %>
<%@ Register Tagprefix="Utilities"
Namespace=”Microsoft.SharePoint.Utilities” Assembly=”Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c” %>
<%@ Register TagPrefix="wssawc"
Namespace=”Microsoft.SharePoint.WebControls” Assembly=”Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c”%>
The “Administration” namespace will be used, amongst other things, to obtain a reference to the SharePoint farm. The “Utilities” namespace will be used for security reasons when validating the page request. The “WebControls” namespace and additional “ascx” controls are used in rendering certain controls on the page.
Add the following Content Placeholders for the title, description, and main content area.
Global Site Feature Management
Global Site Feature Management
Use this page to activate or deactivate site-scoped Features across all sites based on the selected site definition for the selected Web Application.
Next we will add an InputForm, ButtonSection, and a WebApplicationSelector controls to the page. This will allow the user to select the Web Application on wish to activate the feature. We use the same controls SharePoint uses on its Central Administration pages to provide a consistent look and feel. We will also add; a RadioButtonList control to display the features, a CheckBox to indicate if the action should be forced, a DropDownList to specify the action (activate or deactivate), and a CheckBoxList to display the Site Definitions. We will encapsulate all of this in an ASP Panel control to toggle the visibility of the form when the user clicks OK.
| <!– ***************************************** USE SHAREPOINT THE ButtonSection CONTROL TO DISPLAY THE “OK” AND “CANCEL” BUTTONS –> <wssuc:ButtonSection runat="server" TopButtons="true" BottomSpacing=”5″ ShowSectionLine=”false”> <asp:Button UseSubmitBehavior="false" runat="server" class=”ms-ButtonHeightWidth” OnClick=”BtnSubmit_Click” Text=”" id=”btnSubmitTop” accesskey=”" Enabled=”true”/> <!– ************************************** <wssuc:InputFormSection runat="server" |
| <SharePoint:WebApplicationSelector id="Selector" runat="server" TypeLabelCssClass=”ms-listheaderlabel” AllowAdministrationWebApplication=”true” /> |
| <!– Use a SharePoint Required Field Validator to ensure that a web application is selected –> <wssawc:InputFormRequiredFieldValidator ID=”ReqValAppPoolPassword” ControlToValidate=”rdSiteFeatures” ErrorMessage=”You must select a Feature to activate” Runat=”server” /> <asp:RadioButtonList ID="rdSiteFeatures" runat="server" CssClass=”ms-listheaderlabel” EnableViewState=”true” /> |
| <asp:DropDownList ID="lstAction" runat="server" EnableViewState=”true”> |
| <asp:CheckBox ID="chkForce" runat="server" EnableViewState=”true” Text=”Force” CssClass=”ms-listheaderlabel” /> |
| <asp:CheckBox ID="chkSelectAll" runat="server" CssClass=”ms-listheaderlabel” Text=”Select All Click here to select all site definitions.” OnCheckedChanged=”chkSelectAll_Changed” AutoPostBack=”true” /> <asp:CheckBoxList ID="chkListSiteDefinitions" runat="server" |
Next we’ll need to populate the list of features. Check the PostBack state of the page, if false, get the available features from the FeatureDefinitions property of the SPFarm object. Iterate through each of the features and add a list item the the feature radio button list for each one that is scoped at the site(web) level.
//Get the regional localeID and Culture Information (Regional Settings)
//these will be used to retrieve the list of features and
//site definitions
int localeID = (int)SPContext.Current.RegionalSettings.LocaleId;
System.Globalization.CultureInfo cInfo = new System.Globalization.CultureInfo(localeID);
//Get a reference to the farm object we will use it
//to retrieve the feature definitions
SPFarm thisFarm = SPFarm.Local;
foreach (SPFeatureDefinition featureDefinition in thisFarm.FeatureDefinitions)
{
//Check that each feature is scoped at the web level and
//that it is not hidden before adding it to the list
if ((featureDefinition.Scope == SPFeatureScope.Web) && !(featureDefinition.Hidden))
{
ListItem siteFeatureItem = new ListItem("" + featureDefinition.GetTitle(cInfo) + "
"
+ featureDefinition.GetDescription(cInfo), featureDefinition.Id.ToString());
rdSiteFeatures.Items.Add(siteFeatureItem);
}
}
To populate the list of site definitions obtain a reference to the SPWebTemplateCollection of the current site definition via the GetWebTemplates method.
//Get the list of available Site Definitions / WebTemplates
foreach (SPWebTemplate webTemplate in SPContext.Current.Site.GetWebTemplates(SPContext.Current.RegionalSettings.LocaleId))
{
//Skip "GLOBAL", it has an ID of 0
if (webTemplate.ID != 0)
{
ListItem webTemplateItem = new ListItem("" + webTemplate.Title + " - ID: " + webTemplate.ID + " Name: " + webTemplate.Name + "
" + webTemplate.Description,webTemplate.Name);
chkListSiteDefinitions.Items.Add(webTemplateItem);
}
}
Finally, you’ll need to write the code that adds or removes the feature from each site. Use the CurrentItem of WebApplicationSelector to get a reference to the selected web application and the underlying site collections. Iterate through each site(web) checking the WebTemplate and Configuration properties of each against the list of selected site definitions. If the web template and configuration of the site are in the list of selected site definitions, call the Add method of SPWeb.Features to activate the feature and the Remove method to deactivate it based on the “action” the user selected.
//*************************************************************
// This method is called when the user clicks the "OK" button
// to activate the site feature.
protected void BtnSubmit_Click(object sender, EventArgs e)
{
//Prepare a string object to display the result
//of the feature activation for each site
string strMessages = "";
//Hide the form
inputForm.Visible = false;
//Get the GUID of the selected Feature to activate
Guid featureID = new Guid(rdSiteFeatures.SelectedValue);
try
{
//Iterate through each of the site collections
//in the selected web applications
foreach (SPSite site in Selector.CurrentItem.Sites)
{
//Disable the CatchAccessDeniedException
//of the site collection to avoid being redirected
//to the "Access Denied" page if access is denied.
site.CatchAccessDeniedException = false;
//Iterate through each site in the site collection
//and activate the selected feature
foreach (SPWeb web in site.AllWebs)
{
//Use a try statement to trap any errors that
//may occur during activation. Errors may occur
//due to feature dependencies, features that
//may already be active, permissions, etc.
//Consider Refactoring this code for better error
//handling
try
{
//Check if the site definition/configuration of the
//current site is included in the list of site definitions
//that the feature will be applied to.
ListItem item = chkListSiteDefinitions.Items.FindByValue(web.WebTemplate + "#" + web.Configuration.ToString());
if ((item != null) & (item.Selected))
{
//Activate the feature
if (lstAction.SelectedValue == "Activate")
{
web.Features.Add(featureID, chkForce.Checked);
strMessages += "
Feature Successfully Activated: " + web.Url + "
";
}
else
{
web.Features.Remove(featureID, chkForce.Checked);
strMessages += "
Feature Successfully Deactivated: " + web.Url + "
";
}
}
}
catch (Exception featureActivationError)
{
//if an error occurs during activation;
//capture the message to display it to the user
//after iterating through all the sites
strMessages += "
Feature Activation Error: " + web.Url + " ERROR:" + featureActivationError.Message + "
";
}
}
//Allow the site collection to continue handling
//access denied exceptions
site.CatchAccessDeniedException = true;
}
}
catch (Exception featureActivationError)
{
//if an error occurs write the error message to
//an error element
strMessages += featureActivationError.Message + "hhhhhh
";
}
}
<?xml version="1.0" encoding="utf-8" ?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
Id="4D4F4516-DDEC-4ea8-82F4-124D1B743984"
Title="Site Scoped Feature Management"
Hidden="FALSE"
Scope="Farm"
ActivateOnDefault="TRUE"
Version="12.0.0.0">
<ElementManifest Location="Elements.xml" />
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<CustomActionGroup
Id="BDDBB947-CBC6-47f7-B57A-6C8BEB0E19D7"
Location="Microsoft.SharePoint.Administration.ApplicationManagement"
Title="Utilities"
Sequence="1000"/>
<CustomAction
Id="E57ECD33-91FD-4fc3-A4E8-E1B932178CB4"
GroupId="BDDBB947-CBC6-47f7-B57A-6C8BEB0E19D7"
Location="Microsoft.SharePoint.Administration.ApplicationManagement"
Sequence="20"
Title="Site Scoped Feature Management"
Description="">
<UrlAction Url="_admin/SiteFeatureManagement.aspx"/>
Programmatically Checking User Roles or Permission Levels in SharePoint 2007
Posted: October 13, 2008 Filed under: Application Pages, Development, MOSS, Security, WSS 3 Comments »Not to be confused with SharePoint groups; Roles, also known as Permission Levels or Role Definitions, are logical groupings of base permissions. These are typically assigned to SharePoint Groups but can also be assigned to individual users. Some samples of out-of-the-box roles or permission levels include; Read, Contribute, Design, Full Control, and Limited Access.
The following code demonstrates how to verify if the current user is in a particular role or has been assigned a specific permission level. The code sample uses SPContext.Current to get a reference to the current site, as such it must be ran under the context of SharePoint (in a web part, or custom application page), to run the code in a console application or windows application you will need to change how the reference to the SPWeb object is obtained.
SPWeb web = SPContext.Current.Web;
//****************************************
// Validate the page request to avoid
// any malicious posts
if (Request.HttpMethod == “POST”)
SPUtility.ValidateFormDigest();
//****************************************
// Get a reference the roles that are
// bound to the current user and the role
// definition to which we need to verify
// the user against
SPRoleDefinitionBindingCollection usersRoles = web.AllRolesForCurrentUser;
SPRoleDefinitionCollection roleDefinitions = web.RoleDefinitions;
SPRoleDefinition roleDefinition = roleDefinitions["Full Control"];
// Check if the user is in the role. If not
// redirect the user to the access denied page
if (usersRoles.Contains(roleDefinition))
{
//*******************************
//Check if post back to run
//code that initiates the page
if (IsPostBack != true)
{
//Do your stuff here
}
}
else
{
Response.Redirect(“/_layouts/accessdenied.aspx”);
}
Using the InputFormSection and InputFormControl to build Custom Application Pages that look like OOTB Pages.
Posted: October 3, 2008 Filed under: Application Pages, Design, MOSS, WSS 4 Comments »I’ve seen this topic covered indirectly and even unintentionally in a number of posts, some of my favorite references include: “Using the SPPropertyBag with Custom Admin Pages in SharePoint” where Steve Graegert dedicates a section of the posting to talk about the “Page Structure”. Another is “Creating Custom Timer Jobs in Windows SharePoint Services 3.0” by Andrew Connell, in this one, the subject is not covered directly but the code provides a great example of how the controls can be used.
In this posting I’ll try to cover the subject directly using a basic application page and creating a very simple form.
We’ll start out with the application page; copy the following code and save it as an ASPX page in the SharePoint Layouts folder (usaually c:\program files\common files\microsoft shared\web server extensions\12\template\layouts\)
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,
PublicKeyToken=71e9bce111e9429c” %>
Sample Application Page
Sample Application Page
This is a page provides an example of how to use the InputFormSection
and InputFormControl control templates to create an application page
that looks like the SharePoint OOTB Application Pages.
When accessing the page from the browser (i.e. http://siteurl/_layouts/pagename.aspx) should yield the following result:
Add a “register” directive for each of the controls in the page as follows:
<%@ Register TagPrefix="wssuc" TagName="ButtonSection"
src=”~/_controltemplates/ButtonSection.ascx” %>
Next we’ll add a table to the “PlaceHolderMain” content place holder as follows. This table will be used to house our controls:
This is a page provides an example of how to use the InputFormSection
and InputFormControl control templates to create an application page
that looks like the SharePoint OOTB Application Pages.
Insert the InputFormSection control to the table as follows:
This is a page provides an example of how to use the InputFormSection
and InputFormControl control templates to create an application page
that looks like the SharePoint OOTB Application Pages.
<wssuc:InputFormSection runat="server"
Title=”Sample Form Section”
Description=”This is a sample form section” >
This change should yield the following result:
Next we will add the “Template_InputFormControls” element, which will contain our actual form controls. This will be displayed in the right section of the form (the light blue section.) We will also add the “ButtonSection” control which we’ll use to house the buttons for our form. See the full code sample below:
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c” %>
<%@ Register TagPrefix="wssuc" TagName="ButtonSection"
src=”~/_controltemplates/ButtonSection.ascx” %>
Sample Application Page
Sample Application Page
This is a page provides an example of how to use the InputFormSection
and InputFormControl control templates to create an application page
that looks like the SharePoint OOTB Application Pages.
| <wssuc:InputFormSection runat="server" Title=”Sample Form Section” Description=”This is a sample form section” > <wssuc:ButtonSection runat="server" TopButtons="true" BottomSpacing=”5″ ShowSectionLine=”false”> <asp:Button UseSubmitBehavior="false" runat="server" class=”ms-ButtonHeightWidth” Text=”OK” id=”BtnSubmitBottom” Enabled=”true”/> |
The result should look as follows:
Table Height Percentages in SharePoint Master Pages
Posted: September 2, 2008 Filed under: Application Pages, Development, Master Pages, SharePoint Leave a comment »You may have noticed an issue while specifying height percentages for tables and cells within SharePoint master pages, they don’t always work. The table height attribute itself has never really been supported by the W3c, and the table row and cell height attributes have been recently deprecated. But why is it that they sometimes seem to work and other times they don’t, even when using the same browser… more importantly why are they so inconsistent in our master pages?
As it turns out most browsers will render the table, table row, and table cell height percentages just fine (maybe with some minor variances across different browsers) as long as they are specified as part of the elements style. But there are a couple of tricks:
Did you provide a height for your html and body elements?…. It may come as a big surprise, but many browsers don’t automatically set the height of the body to be the same as the height of the window. Your table and or cell height may very well be expanding to 100% of the body, its just that the body itself its not 100% of the window; this can be easily solved by incorporating the following style in your pages or CSS:
html,body{
margin:0;
padding:0;
height:100%;
border:none }
You may be wondering why you haven’t run into this in the past, and perhaps it has to do with the next subject; the document declaration. I’ve seen some sample master pages that include only part of it, or don’t include it at all. If you’ve used some of those samples, the height issue… well it may not have been an issue at all. Why? Lets dig in a little deeper.
Check your document declaration, it declares what version of HTML is used in the document. The W3C provides 3 different declarations:
- The HTML 4.01 Strict DTD – includes all elements and attributes that have not been deprecated or do not appear in frameset documents.
- The HTML 4.01 Transitional DTD – includes everything in the strict DTD plus deprecated elements and attributes (most of which concern visual presentation).
- The HTML 4.01 Frameset DTD – includes everything in the transitional DTD plus frames as well.
Most (if not all) of the out-of-the-box master pages use the Transitional DTD. Its declared in each master page as follows:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
Failing to include this directive or declaration in your pages (or including it partially) tells the browser that the page follows no actual HTML standard.. or at least fails to tell the browser which HTML standard the page follows. Either way the browser takes its best shot, and surprisingly tends to do a better job in understanding what we meant the page to look like. Now, you could remove the declaration altogether; but that would simply be the lazy way out. Including the declarations will probably result in your pages rendering better across existing browsers and next generation browsers.
SharePoint Site Enumeration Page: Get a list of all the sites in your SharePoint farm from Central Administration
Posted: July 25, 2008 Filed under: Application Pages, CustomActions, Development, Feature Development, Features, MOSS, SharePoint, WSS 14 Comments »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:
- 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.
- Open the folder and create a new empty file naming it FEATURE.xml
- 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″> - Save and close the file.
- 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” />
- Save and close the file
- Package the EnumerateSites.aspx page and feature using your method of choice. Don’t have one? Checkout WSPBuilder by Carsten Keutmann
- 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.
Click on the link to get an XML page enumerating all of the sites in your farm, it should look similar to the following
Click here to download the Solution Package (WSP)
Click here to download the source files
Hidden SharePoint Lists, Fields, and other Advanced List Settings
Posted: June 20, 2008 Filed under: .NET, Application Pages, ASP, MOSS, SharePoint, WSS 22 Comments »We often have to create solutions involving hidden lists and libraries. If our hidden list is part of a SharePoint feature, we would probably handle the logic of hiding the list during our feature activation routine. But what if the solution is not part of a feature? Maybe the lists where created using the browser. What if we need to make our hidden list visible to help troubleshoot something that has gone wrong?
I’ve create a SharePoint Application Page that uses the SharePoint Object Model and a series of ASP.Net controls to display and modify properties of SharePoint Lists and Fields in a site. For security reasons only users with full control of the site can access the page.
The user can select the list from an ASP.Net list control. Upon selecting the list, the page exposes the Hidden, AllowDeletion, RequestAccessEnabled, and AllowEveryoneViewItems properties of the selected list.
I expose the list fields during the same time (when the user selects a list.) Upon selecting a field from the list, the page exposes the Hidden property of the selected field.
The following is the code for the application page:
private const string webAppPropertyKey = “Custom404Path”;
protected override void OnLoad(EventArgs e)
{
using (SPWeb site = SPContext.Current.Site.OpenWeb())
{
//****************************************
// Validate the page request to avoid
// any malicious posts
if (Request.HttpMethod == “POST”)
SPUtility.ValidateFormDigest();
//****************************************
// Get a reference the roles that are
// bound to the user and the role
// definition to which we need to verify
// the user against
SPRoleDefinitionBindingCollection usersRoles = site.AllRolesForCurrentUser;
SPRoleDefinitionCollection siteRoleCollection = site.RoleDefinitions;
SPRoleDefinition roleDefinition = siteRoleCollection["Full Control"];
//Check if the user is in the role
if ((usersRoles.Contains(roleDefinition)) || site.CurrentUser.IsSiteAdmin)
{
//*******************************
//Check if post back to run
//code that initiates the page
if (IsPostBack != true)
{
InitPage();
}
}
else
{
Response.Redirect(“/_layouts/accessdenied.aspx”);
}
}
}
//*********************************************
// This method populates the listbox with
// all of the lists(SPList) in the site(SPWeb)
public void InitPage()
{
//Get a reference to the SPWeb object
using (SPWeb site = SPContext.Current.Site.OpenWeb())
{
//Iterate through each of the lists(SPList) in the site
foreach (SPList thislist in site.Lists)
{
//Create a listItem for each list.
//Set the “text” property to the title
//and the value to the list ID
//we will use the list ID to retrieve the
//list settings when the user selects it
ListItem item = new ListItem(thislist.Title, thislist.ID.ToString());
lstSiteLists.Items.Add(item);
}
}
}
//*************************************************
// This method retrieves some of the list settings
// that are not accessible via the site or list settings
// pages on the SharePoint site
public void GetListSettings(object sender, System.EventArgs e)
{
//make the list settings panel visible
//and enable to the “Update List” button
pnlListSettings.Visible = true;
btnUpdateList.Enabled = true;
//get the ID of the selected list
Guid listGuid = new Guid(lstSiteLists.SelectedValue);
//get a reference to the site(SPWeb) object
using (SPWeb site = SPContext.Current.Site.OpenWeb())
{
//get a reference to the list object using
//the list ID
SPList list = site.Lists[listGuid];
//Retrieve and display the ID, template name,
//and template type of the list
lblListGUID.Text = list.ID.ToString();
lblListBaseTemplate.Text = list.BaseTemplate.ToString();
lblListBaseType.Text = list.BaseType.ToString();
//check the “Hidden”, “AllowDelete”, “RequestAccessEnabled”
//and “AllowEveryOneviewItems” properties of the list and
//update the corresponding checkboxes accordingly
chkListHidden.Checked = list.Hidden;
chkListAllowDelete.Checked = list.AllowDeletion;
chkListAllowRequestAccess.Checked = list.RequestAccessEnabled;
chkAllowEveryoneViewItems.Checked = list.AllowEveryoneViewItems;
//Disable the AllowRequest access checkbox if
//the list is inheriting permissions as this
//will be inherited as well
if (list.Permissions.Inherited)
{
chkListAllowRequestAccess.Enabled = false;
}
else
{
chkListAllowRequestAccess.Enabled = true;
}
//Call the method that populates the “Field” listbox
//with the fields(SPField) that are available in the selected
//list
GetListFields(list);
}
}
//**************************************************
//This method populates the “Field” List box with
//the fields(SPField) that are available in the
//list(SPList) that is passed in
public void GetListFields(SPList list)
{
//clear the list box before populating it
//(the user may have selected a new list)
lstFields.Items.Clear();
//hide the field settings panel as
//the user will need to select a new list
pnlFieldSettings.Visible = false;
//disable the update field button
//as the user will need to select a new list
btnUpdateField.Enabled = false;
//iterate through each field(SPField) in the list(SPList)
foreach (SPField field in list.Fields)
{
//Create a listItem for each field.
//Set the “text” property to the title
//and the value to the field ID
//we will use the field ID to retrieve the
//fields settings when the user selects it
ListItem item = new ListItem(field.Title + ” [" + field.InternalName + "]“, field.Id.ToString());
lstFields.Items.Add(item);
}
}
//*************************************************
// This method retrieves some of the field settings
// that are not accessible via the site or list settings
// pages on the SharePoint site
public void GetFieldSettings(object sender, System.EventArgs e)
{
//Make the field settings panel visible
//and enable the field settings button
pnlFieldSettings.Visible = true;
btnUpdateField.Enabled = true;
//Get the ID of the selected list and
//the ID of the field. We will use these
//to retrieve the list and field settings
Guid listGuid = new Guid(lstSiteLists.SelectedValue);
Guid fieldGuid = new Guid(lstFields.SelectedValue);
using (SPWeb site = SPContext.Current.Site.OpenWeb())
{
//get a reference to the list and field
//from the list.
SPList list = site.Lists[listGuid];
SPField field = list.Fields[fieldGuid];
//display the Field ID and internal name
lblFieldID.Text = field.Id.ToString();
lblFieldInternalName.Text = field.InternalName;
//check the CanToggleHidden Attribute of the field
//and if false display a warning
if (!(field.CanToggleHidden))
{
lblFieldWarning.Text = “Warning: The CanToggleHidden property of this field has been set to false; changing its \”hidden\” property could render your application unstable.”;
}
else
{
lblFieldWarning.Text = “”;
}
//retrieve the “hidden” property value
//of the field and set the corresponding
//checkbox
chkFieldHidden.Checked = field.Hidden;
}
}
//**************************************************
//This method updates the list settings according
//to the values specified in the form
public void UpdateList(object sender, System.EventArgs e)
{
//Get the ID of the selected list
Guid listGuid = new Guid(lstSiteLists.SelectedValue);
using (SPWeb site = SPContext.Current.Site.OpenWeb())
{
SPList list = site.Lists[listGuid];
list.AllowDeletion = chkListAllowDelete.Checked;
if (!(list.Permissions.Inherited))
{
list.RequestAccessEnabled = chkListAllowRequestAccess.Checked;
}
list.Hidden = chkListHidden.Checked;
list.AllowEveryoneViewItems = chkAllowEveryoneViewItems.Checked;
list.Update();
site.AllowUnsafeUpdates = false;
}
}
public void UpdateField(object sender, System.EventArgs e)
{
Guid listGuid = new Guid(lstSiteLists.SelectedValue);
Guid fieldGuid = new Guid(lstFields.SelectedValue);
using (SPWeb site = SPContext.Current.Site.OpenWeb())
{
SPList list = site.Lists[listGuid];
SPField field = list.Fields[fieldGuid];
lblFieldID.Text = field.Id.ToString();
lblFieldInternalName.Text = field.InternalName;
if (!(field.CanToggleHidden))
{
Type type = field.GetType();
MethodInfo mi = type.GetMethod(“SetFieldBoolValue”, BindingFlags.NonPublic | BindingFlags.Instance);
mi.Invoke(field, new object[] { “CanToggleHidden”, true });
}
field.Hidden = chkFieldHidden.Checked;
field.Update();
}
}
| Select List:
|
List Settings ID: Base Template: Base Type:
(must first turn off permission inheritance)
|
| Fields
|
Field Settings ID: Internal Name:
|
Advanced List Settings
Advanced List Settings
Click here to download a zip file containing the Application Page.
Updated 8/26/2008: Changed how I obtained the reference to the SPWeb object throughout the code. The code was originally obtaining a reference to the SPWeb object via the current context from within a “using” statement. This was wrong as disposing of a shared resource such as an SPWeb object obtained from the current context may cause the SharePoint object model to behave unpredictably. Alternatively I could have removed the using statements and let SharePoint manage the object, but considering the number of lines of code it was easier to do a find and replace from “this.Web” to “SPContext.Current.Site.OpenWeb()” which does not return a reference to a shared resource. See Best Practices: Using Disposable Windows SharePoint Services Objects for more information on the subject.
Technorati Tags: SharePoint, WSS, MOSS, ASP, NET
