In this post I will describe how you can create an application page using SharePoint.Portal webcontrols instead of writing all the HTML yourself.. ;) And in addition I'm gonna describe how to make use of the property bag to store the configuration settings in.
So this is our admin page that is hosted in the Central Administration of the farm. From this admin page we can specify all the LifeCycleManagement (LCM) features. Pretty fancy eh? ;)
Here is how the controls look like under the hood (without any code yet)
<table width="100%" border="0" cellspacing="0" cellpadding="0" class="ms-propertysheet">
<wssuc:InputFormSection Title="Backup Settings" id="ifmSQL"
Description="Specify the settings for the site and web deletion capture" runat="server">
<template_inputformcontrols>
<wssuc:InputFormControl LabelText="Specify backup location (e.g. \\fileshare\SharePointBackups\" runat="server">
<Template_Control>
<wssawc:InputFormTextBox CssClass="ms-input"
ID="txtBackuplocation" Runat="server" Columns="60" />
</Template_Control>
</wssuc:InputFormControl>
<wssuc:InputFormControl LabelText="Specify filename policy" runat="server"
Description="Specify filename policy">
<Template_Control>
<SPSWC:InputFormCheckBox CssClass="ms-input" ID="cbxDateTime"
runat="server" Text="Include date time in filename?" />
<SPSWC:InputFormCheckBox CssClass="ms-input" ID="cbxFolderSitecollection"
runat="server" Text="Create folder per sitecollection?" />
<SPSWC:InputFormCheckBox CssClass="ms-input" ID="cbxFolderWeb"
runat="server" Text="Create (sub)folder per web?" />
</Template_Control>
</wssuc:InputFormControl>
</template_inputformcontrols>
</wssuc:InputFormSection>
<wssuc:InputFormSection Title="Install eventhandler" id="fmEventHandler"
Description="Specify which on which application you want to install the site deletion capture event handler" runat="server">
<template_inputformcontrols>
<wssuc:InputFormControl LabelText="Web Application" runat="server">
<Template_Control>
<SPSWC:InputFormDropDownList CssClass="ms-input" ID="drpWebApps"
runat="server"/>
</Template_Control>
</wssuc:InputFormControl>
<wssuc:InputFormControl LabelText="Event Handler" runat="server">
<Template_Control>
<SPSWC:InputFormLinkButton Text="Install" OnClick="btn_Install" ID="btnInstall" runat="server" />
<SPSWC:InputFormLinkButton Text="Check" OnClick="btn_Check" ID="btnCheck" runat="server" />
</Template_Control>
</wssuc:InputFormControl>
<wssuc:InputFormControl LabelText="Timer Job" runat="server">
<Template_Control>
<SPSWC:InputFormLinkButton Text="Install" OnClick="btn_InstallTimerJob" ID="btnInstallTimerJob" runat="server" />
<SPSWC:InputFormLinkButton Text="UnInstall" OnClick="btn_UnInstallTimerJob" ID="btnUnInstallTimerJob" runat="server" />
</Template_Control>
</wssuc:InputFormControl>
</template_inputformcontrols>
</wssuc:InputFormSection>
<wssuc:InputFormSection Title="Creates LCM Logging & Analytical Web" id="fmLCMWeb"
Description="Creates a admin web with lists to display logging and statistical information." runat="server">
<template_inputformcontrols>
<wssuc:InputFormControl LabelText="Creates Web & Lists" runat="server">
<Template_Control>
<SPSWC:InputFormLinkButton Text="Create Web" OnClick="btn_CreateWeb" ID="btnCreateWeb" runat="server" />
</Template_Control>
</wssuc:InputFormControl>
</template_inputformcontrols>
</wssuc:InputFormSection>
<wssuc:InputFormSection Title="Statistics administration" id="fmLCMStatistisc"
Description="Specify what all the details are to run the statistics on" runat="server">
<template_inputformcontrols>
<wssuc:InputFormControl LabelText="Specify which webapplication hosts the MySites" runat="server">
<Template_Control>
<SPSWC:InputFormDropDownList CssClass="ms-input" ID="drpWebApps2" runat="server"/>
</Template_Control>
</wssuc:InputFormControl>
</template_inputformcontrols>
</wssuc:InputFormSection>
<SPSWC:InputFormButtonSection runat="server">
<SPSWC:InputFormButtonAtBottom runat="server" ID="btnOK" OnClick="OnClickOK"
TextLocId="Page_OkButton_Text" />
<SPSWC:InputFormButtonAtBottom runat="server" ID="btnCancel" OnClick="OnClickCancel"
TextLocId="Page_CancelButton_Text" CausesValidation="false" />
</SPSWC:InputFormButtonSection>
</table>
Nice eh? You don't have to worry about the layout, SharePoint takes care of that! Especially useful if you create this pages with sites that are themed ;) So the next question is.. how do you store all the settings that you can specify? Well you just add some script to the page with some .NET code in it. For example this is the code that is being used when you click on 'OK' (which is located at the bottom of the page (which you cannot see in the screenshot)) :
protected void OnClickOK(Object Sender, EventArgs e)
{
try
{
SetWebProperty(cbxDateTime.Checked.ToString(),"bDateTime",web.Properties);
SetWebProperty(cbxFolderSitecollection.Checked.ToString(),"bFolderSitecollection",web.Properties);
SetWebProperty(cbxFolderWeb.Checked.ToString(),"bFolderWeb",web.Properties);
SetWebProperty(txtBackuplocation.Text,"backuplocation",web.Properties);
SetWebProperty("true","Configured",web.Properties);
SetWebProperty(drpWebApps2.SelectedValue.ToString(),"MySitesWebApp",web.Properties);
}
catch
{
SetWebProperty("false","Configured",web.Properties);
}
Response.Redirect("/_admin/applications.aspx");
}
private static string GetWebProperty(string propertyName, SPPropertyBag siteWebProperties)
{
string propertyname = "LCM_" + propertyName;
string returnvalue;
if (siteWebProperties[propertyname] == null)
{
returnvalue = "";
}
else
{
returnvalue = siteWebProperties[propertyname];
}
return returnvalue;
}
private static void SetWebProperty(string propertyValue, string propertyName, SPPropertyBag siteWebProperties)
{
string propertyname = "LCM_" + propertyName;
if (siteWebProperties[propertyname] == null)
{
siteWebProperties.Add(propertyname, propertyValue);
}
else
{
siteWebProperties[propertyname] = propertyValue;
}
siteWebProperties.Update();
}
Very powerful stuff this PropertyBag! Instead of storing data in a list or writing a webpart to store the properties in or write them to SQL database table this is much easier and less resource heavy. Kinda like using cookies ;)
The beautiful thing is that all the .aspx pages that are located in the _layouts folder have permission to execute code. So you won't have to include an exception in the web.config file.If you are interested in the whole page (without any comments yet (don't worry that will be in there in the final version that will be released to CodePlex! :)) then here you go :
<%@ Page Language="C#" MasterPageFile="~/_layouts/application.master" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="wssuc" TagName="InputFormSection" Src="~/_controltemplates/InputFormSection.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="InputFormControl" Src="~/_controltemplates/InputFormControl.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ButtonSection" Src="~/_controltemplates/ButtonSection.ascx" %>
<%@ Register TagPrefix="wssawc" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="SPSWC" Namespace="Microsoft.SharePoint.Portal.WebControls" Assembly="Microsoft.SharePoint.Portal, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ 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="LifeCycleManagement" Namespace="LifeCycleManagement" Assembly="LifeCycleManagement, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4306074270f0265a" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="Microsoft.SharePoint.Administration" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="LifeCycleManagement" %>
<script runat="server">
SPWeb web = SPContext.Current.Web;
protected override void OnLoad(EventArgs e)
{
//SPWeb web = SPContext.Current.Web;
if (!this.IsPostBack)
{
SPFarm farm = SPFarm.Local;
SPWebService service = farm.Services.GetValue<SPWebService>("");
foreach (SPWebApplication webApp in service.WebApplications)
{
ListItem item = new ListItem();
item.Value = webApp.GetResponseUri(SPUrlZone.Default).ToString();
item.Text = webApp.Name.ToString();
drpWebApps.Items.Add(item);
drpWebApps2.Items.Add(item);
}
if (GetWebProperty("Configured", web.Properties) == "true")
{
txtBackuplocation.Text = GetWebProperty("backuplocation", web.Properties);
cbxDateTime.Checked = Convert.ToBoolean(GetWebProperty("bDateTime", web.Properties));
cbxFolderSitecollection.Checked = Convert.ToBoolean(GetWebProperty("bFolderSitecollection", web.Properties));
cbxFolderWeb.Checked = Convert.ToBoolean(GetWebProperty("bFolderWeb", web.Properties));
}
if (GetWebProperty("LCMSiteCreated", web.Properties) == "true")
{
//btnCreateWeb.Enabled = false;
//btnCreateWeb.Text = "Site already created";
}
output.Text += "Number of sites: " + GetWebProperty("MySitesWebApp", web.Properties);
}
base.OnLoad(e);
}
protected void OnClickOK(Object Sender, EventArgs e)
{
try
{
SetWebProperty(cbxDateTime.Checked.ToString(),"bDateTime",web.Properties);
SetWebProperty(cbxFolderSitecollection.Checked.ToString(),"bFolderSitecollection",web.Properties);
SetWebProperty(cbxFolderWeb.Checked.ToString(),"bFolderWeb",web.Properties);
SetWebProperty(txtBackuplocation.Text,"backuplocation",web.Properties);
SetWebProperty("true","Configured",web.Properties);
SetWebProperty(drpWebApps2.SelectedValue.ToString(),"MySitesWebApp",web.Properties);
}
catch
{
SetWebProperty("false","Configured",web.Properties);
}
Response.Redirect("/_admin/applications.aspx");
}
private static string GetWebProperty(string propertyName, SPPropertyBag siteWebProperties)
{
string propertyname = "LCM_" + propertyName;
string returnvalue;
if (siteWebProperties[propertyname] == null)
{
returnvalue = "";
}
else
{
returnvalue = siteWebProperties[propertyname];
}
return returnvalue;
}
private static void SetWebProperty(string propertyValue, string propertyName, SPPropertyBag siteWebProperties)
{
string propertyname = "LCM_" + propertyName;
if (siteWebProperties[propertyname] == null)
{
siteWebProperties.Add(propertyname, propertyValue);
}
else
{
siteWebProperties[propertyname] = propertyValue;
}
siteWebProperties.Update();
}
protected void OnClickCancel(Object Sender, EventArgs e)
{
Response.Redirect("/_admin/applications.aspx");
}
protected void btn_Install(Object Sender, EventArgs e)
{
FeatureEventHandler(true);
}
protected void btn_Check(Object Sender, EventArgs e)
{
FeatureEventHandler(false);
}
protected void btn_InstallTimerJob(Object Sender, EventArgs e)
{
InstallTimerJob();
}
protected void btn_UnInstallTimerJob(Object Sender, EventArgs e)
{
UnInstallTimerJob();
}
protected void btn_CreateWeb(Object Sender, EventArgs e)
{
CreateWeb();
CreateList();
}
protected void FeatureEventHandler(bool Install)
{
Guid guid = new Guid("080ed5e5-3791-4433-a668-03b3902178a6");
SPFarm farm = SPFarm.Local;
SPWebService service = farm.Services.GetValue<SPWebService>("");
SPWebApplication wa = service.WebApplications[drpWebApps.SelectedItem.ToString()];
foreach (SPSite site in wa.Sites)
{
foreach(SPWeb _web in site.AllWebs)
{
if (Install)
{
try
{
_web.AllowUnsafeUpdates = true;
_web.Features.Add(guid);
output.Text += "<BR>Installing at : " + _web.Title.ToString() + " " + _web.Url.ToString();
}
catch(Exception Installerror)
{
output.Text += "<BR>Could not install at : " + _web.Title.ToString() + " " + _web.Url.ToString() + "due to " + Installerror.Message.ToString();
}
}
else
{
try
{
SPFeature feature = _web.Features[guid];
output.Text += "<BR>Installed at : " + _web.Title.ToString() + " " + _web.Url.ToString();
}
catch(Exception Checkerror)
{
output.Text += "<BR>Not Installed at : " + _web.Title.ToString() + " " + _web.Url.ToString() + "due to " + Checkerror.Message.ToString();;
}
}
}
}
}
private void InstallTimerJob()
{
SPFarm farm = SPFarm.Local;
SPWebService service = farm.Services.GetValue<SPWebService>("");
SPWebApplication wa = service.WebApplications[drpWebApps.SelectedItem.ToString()];
GatherStatistics taskLoggerJob = new GatherStatistics("GatherStatistics", wa);
SPMinuteSchedule schedule = new SPMinuteSchedule();
schedule.BeginSecond = 0;
schedule.EndSecond = 59;
schedule.Interval = 2;
taskLoggerJob.Schedule = schedule;
taskLoggerJob.Update();
}
private void UnInstallTimerJob()
{
SPFarm farm = SPFarm.Local;
SPWebService service = farm.Services.GetValue<SPWebService>("");
SPWebApplication wa = service.WebApplications[drpWebApps.SelectedItem.ToString()];
GatherStatistics taskLoggerJob = new GatherStatistics("GatherStatistics", wa);
foreach (SPJobDefinition job in wa.JobDefinitions)
{
if (job.Name == "GatherStatistics")
{
job.Delete();
}
}
}
private void CreateWeb()
{
try
{
SPWeb LCMweb = web.Site.AllWebs.Add("LCM", "Life Cycle Management Logging and Analytical Site", "", Convert.ToUInt32(1033), "STS#0", false, false);
SetWebProperty("true","LCMSiteCreated",web.Properties);
output.Text += "<BR>" + LCMweb.Url.ToString();
}
catch(Exception sitecreationerror)
{
SetWebProperty("false","LCMSiteCreated",web.Properties);
output.Text += "<BR>" + sitecreationerror.Message.ToString();
}
}
private void CreateList()
{
SPWeb LCMWeb = web.Site.OpenWeb("LCM");
LCMWeb.Lists.Add("EnterpriseFeatures", "Overview of all the sites and webs that have the enterprise features enabled", SPListTemplateType.GenericList);
SPList EnterpriseFeaturesList = LCMWeb.Lists["EnterpriseFeatures"];
EnterpriseFeaturesList.EnableAttachments = false;
EnterpriseFeaturesList.EnableVersioning = false;
EnterpriseFeaturesList.Fields.Add("Features", SPFieldType.Text, false);
EnterpriseFeaturesList.Fields.Add("SiteOwner", SPFieldType.Text, false);
EnterpriseFeaturesList.Fields.Add("Url", SPFieldType.Text, false);
EnterpriseFeaturesList.Update();
LCMWeb.Lists.Add("DeletedSites", "Overview of all the sites and webs that have been deleted", SPListTemplateType.GenericList);
SPList DeletedSitesList = LCMWeb.Lists["DeletedSites"];
DeletedSitesList.EnableAttachments = false;
DeletedSitesList.EnableVersioning = false;
DeletedSitesList.Fields.Add("SiteCollection", SPFieldType.Boolean, false);
DeletedSitesList.Fields.Add("Web", SPFieldType.Boolean, false);
DeletedSitesList.Fields.Add("Url", SPFieldType.Text, false);
DeletedSitesList.Fields.Add("Date", SPFieldType.DateTime, false);
DeletedSitesList.Fields.Add("Size", SPFieldType.Text, false);
DeletedSitesList.Update();
LCMWeb.Lists.Add("UnusedSites", "Overview of all the sites that are not being used for a period of time", SPListTemplateType.GenericList);
SPList UnusedSites = LCMWeb.Lists["UnusedSites"];
UnusedSites.EnableAttachments = false;
UnusedSites.EnableVersioning = false;
UnusedSites.Fields.Add("Url", SPFieldType.Text, false);
UnusedSites.Fields.Add("LastContentModifiedDate", SPFieldType.DateTime, false);
UnusedSites.Fields.Add("Size", SPFieldType.Text, false);
UnusedSites.Update();
}
</script>
<asp:Content ID="Content2" ContentPlaceHolderID="PlaceHolderPageTitle" runat="server">
<SharePoint:EncodedLiteral runat="server" Text="Life Cycle Management Setting" EncodeMethod='HtmlEncode' />
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server">
<SharePoint:EncodedLiteral runat="server" Text="Life Cycle Management Settings" EncodeMethod='HtmlEncode' />
</asp:Content>
<asp:Content ContentPlaceHolderID="PlaceHolderPageImage" runat="server">
</asp:Content>
<asp:Content ID="contentMain" ContentPlaceHolderID="PlaceHolderMain" runat="server">
<style type="text/css">
table.ms-propertysheet {
height: 100%;
}
</style>
<table cellspacing="0" cellpadding="0" border="0" class="ms-settingsframe">
<tr>
<td width="100%" colspan="4" style="padding-top: 0px;">
<table class="ms-pageinformation" width="100%" cellpadding="0" cellspacing="0">
<tr>
<td valign="top" style="padding: 10px;" width="100%" height="100px">
<table height="100%" width="100%" id="idItemHoverTable">
<tr>
<th scope="col" colspan="2" style="padding-bottom: 8px;">
<span class="ms-linksectionheader">
<h3 class="ms-standardheader">
<SharePoint:EncodedLiteral
ID="EncodedLiteral1"
runat="server"
Text="Admin page for setting LCM preferences"
EncodeMethod='HtmlEncode' />
</h3>
</span>
</th>
</tr>
<tr>
<th scope="row" nowrap="nowrap">
<SharePoint:EncodedLiteral
ID="EncodedLiteral2"
runat="server" Text="<% $Resources:wss,settings_siteurl %>"
EncodeMethod='HtmlEncode' />:
</th>
<td dir="ltr">
<% SPHttpUtility.HtmlEncode(web.Url + "/", Response.Output); %>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td valign="top" style="padding: 4px 0px 4px 0px;" height="100%">
<table width="100%" border="0" cellspacing="0" cellpadding="0" class="ms-propertysheet">
<wssuc:InputFormSection Title="Backup Settings" id="ifmSQL"
Description="Specify the settings for the site and web deletion capture" runat="server">
<template_inputformcontrols>
<wssuc:InputFormControl LabelText="Specify backup location (e.g. \\fileshare\SharePointBackups\" runat="server">
<Template_Control>
<wssawc:InputFormTextBox CssClass="ms-input"
ID="txtBackuplocation" Runat="server" Columns="60" />
</Template_Control>
</wssuc:InputFormControl>
<wssuc:InputFormControl LabelText="Specify filename policy" runat="server"
Description="Specify filename policy">
<Template_Control>
<SPSWC:InputFormCheckBox CssClass="ms-input" ID="cbxDateTime"
runat="server" Text="Include date time in filename?" />
<SPSWC:InputFormCheckBox CssClass="ms-input" ID="cbxFolderSitecollection"
runat="server" Text="Create folder per sitecollection?" />
<SPSWC:InputFormCheckBox CssClass="ms-input" ID="cbxFolderWeb"
runat="server" Text="Create (sub)folder per web?" />
</Template_Control>
</wssuc:InputFormControl>
</template_inputformcontrols>
</wssuc:InputFormSection>
<wssuc:InputFormSection Title="Install eventhandler" id="fmEventHandler"
Description="Specify which on which application you want to install the site deletion capture event handler" runat="server">
<template_inputformcontrols>
<wssuc:InputFormControl LabelText="Web Application" runat="server">
<Template_Control>
<SPSWC:InputFormDropDownList CssClass="ms-input" ID="drpWebApps"
runat="server"/>
</Template_Control>
</wssuc:InputFormControl>
<wssuc:InputFormControl LabelText="Event Handler" runat="server">
<Template_Control>
<SPSWC:InputFormLinkButton Text="Install" OnClick="btn_Install" ID="btnInstall" runat="server" />
<SPSWC:InputFormLinkButton Text="Check" OnClick="btn_Check" ID="btnCheck" runat="server" />
</Template_Control>
</wssuc:InputFormControl>
<wssuc:InputFormControl LabelText="Timer Job" runat="server">
<Template_Control>
<SPSWC:InputFormLinkButton Text="Install" OnClick="btn_InstallTimerJob" ID="btnInstallTimerJob" runat="server" />
<SPSWC:InputFormLinkButton Text="UnInstall" OnClick="btn_UnInstallTimerJob" ID="btnUnInstallTimerJob" runat="server" />
</Template_Control>
</wssuc:InputFormControl>
</template_inputformcontrols>
</wssuc:InputFormSection>
<wssuc:InputFormSection Title="Creates LCM Logging & Analytical Web" id="fmLCMWeb"
Description="Creates a admin web with lists to display logging and statistical information." runat="server">
<template_inputformcontrols>
<wssuc:InputFormControl LabelText="Creates Web & Lists" runat="server">
<Template_Control>
<SPSWC:InputFormLinkButton Text="Create Web" OnClick="btn_CreateWeb" ID="btnCreateWeb" runat="server" />
</Template_Control>
</wssuc:InputFormControl>
</template_inputformcontrols>
</wssuc:InputFormSection>
<wssuc:InputFormSection Title="Statistics administration" id="fmLCMStatistisc"
Description="Specify what all the details are to run the statistics on" runat="server">
<template_inputformcontrols>
<wssuc:InputFormControl LabelText="Specify which webapplication hosts the MySites" runat="server">
<Template_Control>
<SPSWC:InputFormDropDownList CssClass="ms-input" ID="drpWebApps2" runat="server"/>
</Template_Control>
</wssuc:InputFormControl>
</template_inputformcontrols>
</wssuc:InputFormSection>
<SPSWC:InputFormButtonSection runat="server">
<SPSWC:InputFormButtonAtBottom runat="server" ID="btnOK" OnClick="OnClickOK"
TextLocId="Page_OkButton_Text" />
<SPSWC:InputFormButtonAtBottom runat="server" ID="btnCancel" OnClick="OnClickCancel"
TextLocId="Page_CancelButton_Text" CausesValidation="false" />
</SPSWC:InputFormButtonSection>
</table>
</td>
</tr>
</table>
<asp:Label id="output" runat="server"/>
</asp:Content>
And the credit really goes to Steve Graegert who wrote this excellent post Using the SPPropertyBag with Custom Admin Pages in SharePoint. So if you want to know in detail how you can implement and design this, please read his post! Thanks Steve! :)