SharePoint: SPPersistedObject, Create an administration page in SharePoint central administration.

Custom administration pages can be used in Central Administration for a variety of purposes. One such scenario can be creating a page in CA for storing database connection string and using it in timer jobs. In this post we will create an administration page and use persisted objects to store data in SharePoint configuration database.
Create an empty SharePoint 2010 project with the following structure:



Copy this markup to the SampleCAAdminpage.aspx page.

<%@ Assembly Name="Microsoft.SharePoint.ApplicationPages.Administration, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %> 
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> 
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> 
<%@ Register Tagprefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
<%@ Import Namespace="Microsoft.SharePoint" %> 
<%@ Assembly Name="Microsoft.Web.CommandUI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="wssuc" TagName="InputFormSection" src="~/_controltemplates/InputFormSection.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ButtonSection" src="~/_controltemplates/ButtonSection.ascx" %>
<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="~/_admin/admin.master" Inherits="SampleCAAdminPage.Pages.SampleCAAdminPage, SampleCAAdminPage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a6ed85d3cc39e3bd" %> 
<asp:Content ID="Content1" ContentPlaceHolderID="PlaceHolderPageTitle" runat="server">
    <SharePoint:EncodedLiteral ID="EncodedLiteral1" runat="server" Text="XYZ Project administration"
        EncodeMethod='HtmlEncode' />
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea"
    runat="server">
    <SharePoint:EncodedLiteral ID="EncodedLiteral2" runat="server" Text="XYZ Project administration"
        EncodeMethod='HtmlEncode' />
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="PlaceHolderPageDescription" runat="server">
    <SharePoint:EncodedLiteral ID="EncodedLiteral3" runat="server" Text="Use this page to configure XYZ Project."
        EncodeMethod='HtmlEncodeAllowSimpleTextFormatting' />
</asp:Content>
<asp:content ID="Content4" contentplaceholderid="PlaceHolderMain" runat="server">
<table border="0" cellspacing="0" cellpadding="0" width="100%">
  <wssuc:InputFormSection runat="server"
                Title="Database Connectionstring"
                Description="Identify the connection string used for accessing the the database." >
                <Template_InputFormControls>
                                <tr><td>
            <SharePoint:InputFormTextBox ID="txtConnectionString" RichText="false" runat="server" Width="100%" TextMode="MultiLine" Rows="5"/>
                                </td></tr>
        <tr><td>
            <SharePoint:InputFormRequiredFieldValidator ID="valConnectionString" ControlToValidate="txtConnectionString" ErrorMessage="Enter a connection string" runat="server" SetFocusOnError="true"></SharePoint:InputFormRequiredFieldValidator>
                                </td></tr>
        <tr><td>
            <asp:Button ID="btnConnectionTest" runat='server' Text="Test Connection"  class="ms-ButtonHeightWidth" OnClick="btnConnectionTest_Click" />
        </td></tr>
                </Template_InputFormControls>
  </wssuc:InputFormSection>

  <wssuc:InputFormSection runat="server"
                Title="SharePoint List"
                Description="Identify the list for data import." >
                <Template_InputFormControls>
                                <tr><td>
            <SharePoint:InputFormTextBox ID="txtListName" RichText="false" runat="server" Width="100%" TextMode="SingleLine"/>
                                </td></tr>
        <tr><td>
            <SharePoint:InputFormRequiredFieldValidator ID="valListName" ControlToValidate="txtListName" ErrorMessage="Enter a list name" runat="server" SetFocusOnError="true"></SharePoint:InputFormRequiredFieldValidator>
                                </td></tr>
                </Template_InputFormControls>
  </wssuc:InputFormSection>
     <wssuc:ButtonSection runat="server">
                                <Template_Buttons>
                                                <asp:Button UseSubmitBehavior="false" runat="server" class="ms-ButtonHeightWidth" Text="<%$Resources:wss,multipages_okbutton_text%>" id="btnSubmit" accesskey="<%$Resources:wss,okbutton_accesskey%>" Enabled="true" OnClick="btnSubmit_Click"/>
                                </Template_Buttons>
    </wssuc:ButtonSection>
</table>
</asp:Content>

 Change the public key token in Page directive line to your value.
<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="~/_admin/admin.master" Inherits="SampleCAAdminPage.Pages.SampleCAAdminPage, SampleCAAdminPage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a6ed85d3cc39e3bd" %> 

Use the following code in connection.cs file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Administration;

namespace SampleCAAdminPage.Configuration
{
    public class Connection : SPPersistedObject
    {
        private readonly static string NAME = typeof(Connection).FullName;

        [Persisted]
        private string mConnectionString;

        [Persisted]
        private string mListName;

        /// <summary>
        /// Initializes a new instance of the <see cref="ClaimsConnection"/> class.
        /// </summary>
        public Connection()
            : base()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ClaimsConnection"/> class.
        /// </summary>
        /// <param name="parent">The parent.</param>
        public Connection(SPPersistedObject parent)
            : base(Connection.NAME, parent)
        {
        }

        public string ListName
        {
            get
            {
                return this.mListName;
            }
            set
            {
                this.mListName = value;
            }
        }

        public string ConnectionString
        {
            get
            {
                return this.mConnectionString;
            }
            set
            {
                this.mConnectionString = value;
            }
        }

        /// <summary>
        /// Gets the ClaimsConnection details from local.
        /// </summary>
        public static Connection Local
        {
            get
            {
                Connection result = SPFarm.Local.GetChild<Connection>(Connection.NAME);
                if (null == result)
                {
                    try
                    {
                        result = new Connection(SPFarm.Local);
                        result.Update();
                    }
                    catch (Exception ex)
                    {
                        return null;
                    }
                }

                return result;

            }
        }
    }
}

Use following code in SampleCAAdminPage.cs class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.ApplicationPages;
using System.Web.UI.WebControls;
using Microsoft.SharePoint.WebControls;
using SampleCAAdminPage.Configuration;
using Microsoft.SharePoint.Utilities;
using System.Web;
using System.Web.UI;
using System.Data.SqlClient;

namespace SampleCAAdminPage.Pages
{
    public class SampleCAAdminPage : ApplicationsManagementPage
    {
        protected InputFormTextBox txtConnectionString;
        protected InputFormTextBox txtListName;
        protected Button btnConnectionTest;
        protected Button btnSubmit;

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            if (!this.IsPostBack)
            {
                Connection connection = Connection.Local;

                this.txtConnectionString.Text = connection.ConnectionString;
                this.txtListName.Text = connection.ListName;
            }
        }

        protected void btnSubmit_Click(object sender, EventArgs e)
        {
            Connection connection = Connection.Local;
            if (null != connection)
            {
                connection.ConnectionString = this.txtConnectionString.Text.Trim();
                connection.ListName = this.txtListName.Text.Trim();
                connection.Update();
                SPUtility.Redirect(PageToRedirectOnOK, SPRedirectFlags.Static, HttpContext.Current);
            }
        }

        protected void btnConnectionTest_Click(object sender, EventArgs e)
        {
            try
            {
                using (SqlConnection connection = new SqlConnection(this.txtConnectionString.Text))
                {
                    connection.Open();
                    connection.Close();
                    ShowMessage(this.Page, "Connection succeeded.");
                }
            }
            catch (Exception ex)
            {
                ShowMessage(this.Page, ex.Message);
            }

        }

        public override string PageToRedirectOnCancel
        {
            get
            {
                return "/applications.aspx";
            }
        }

        public override string PageToRedirectOnOK
        {
            get
            {
                return "/applications.aspx";
            }
        }

        public static void ShowMessage(Page page, string text)
        {
            // build up javascript to inject at the tail end of the page
            StringBuilder stringBuilder = new StringBuilder();

            stringBuilder.AppendLine("<script type=\"text/javascript\">");
            stringBuilder.AppendLine(string.Format("alert(\"{0}\");", text));
            stringBuilder.AppendLine("</script>");

            // add to the page
            page.Controls.Add(new LiteralControl(stringBuilder.ToString()));
        }

    }
}
Use follwoing xml in Elements file of CustomAction element:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
       <CustomActionGroup
    Id="XYZConfiguration"
    Location="Microsoft.SharePoint.Administration.Applications"
    Title="XYZ Configuration"
    Sequence="100"
       ImageUrl="/_layouts/images/CentralAdmin_Security_GeneralSecurity_32x32.png"
 />
       <CustomAction
                        Id="XYZConfiguration.AdminPage"
                        GroupId="XYZConfiguration"
                        Location="Microsoft.SharePoint.Administration.Applications"
                        Sequence="6"
                        Title="Configure XYZ Project"
                        Description="Use this to configure XYZ Project connection options"
                        UIVersion="4">
              <UrlAction Url="/_admin/SampleCAAdminPage/SampleCAAdminPage.aspx"/>
       </CustomAction>
</Elements>

Ensure that the feature has Farm level scope:
Finally deploy the solution and go to CA and click on Application management. Notice the section for configuring XYZ project.
Enter and save database connection string and list name in the page.

Inside the timer job or any SharePoint code the above saved values can be retrieved as follows:
Connection con = Connection.Local;
string connectionString = con.ConnectionString;
string listName = con.ListName;