Creating, Using SPPersistedObjects and creating with PowerShell


SPPersistedObjects are something that now I know what they are and how to use them, I wish I’d known about them earlier. There has been many projects I’ve done in the past where I could have used them.

There is nothing worst when writing code you have to have some configuration information, but you know that in different environments (dev, staging, production) the values will be different. In ASP.NET projects if you wanted to store some configuration data, you normally put it in the Web.config of the application. Although you can do this, it is a pain, as you need to ensure you update every web.config within the farm for the given web application.

The SPPersistedObject lets you store hierarchical configuration data for your application in SharePoint. By marking attributes with [Persisted] SharePoint will serialize the object to XML and place it within the SharePoint Configuration database. Then within your page/webpart etc you can retrieve this information. For each environment you would be able to change the values. The downside of SPPersistedObjects is that there are no Administrative pages to configure/change your PersistedObjects. Therefore when you create a PersistedObject you should create the corresponding Administration page. You can also configure the PersistedObject using powershell.

In my example below I will be creating a simple custom SPPersistedObject, a custom administration page, and I will also include the powershell how to change the values. This example is purely to show you how everything fits together, the 3 values I’m persisted together have no real reason behind them, it just showing you what you could do.

I’ve created an Empty SharePoint Project and added a C# class called PersistedDatabaseSettings.cs I’ve added the reference Microsoft.SharePoint.Administration, System.Runtime.InteropServices and inherited my class with SPPersistedObject. I’ve marked the class with a GUID and lastly I’ve given my Custom SPPersistedObject a name to be able to reference it later. CFSP ExternalDatabaseConnection

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
namespace CFSP.PersistedObjects
{
[Guid("8F0D36AC-B492-4b7f-9B96-373BFB22B350")]
public class PersistedCannonFodderSettings : SPPersistedObject
{
 private const string PROPERTY_NAME = "CFSP ExternalDatabaseConnection";
 public PersistedCannonFodderSettings() { }
 public PersistedCannonFodderSettings(SPPersistedObject parent):base(PROPERTY_NAME, parent){ }

/// <summary>
/// Override this method to allow more users to update the object.
/// </summary>
/// <returns></returns>
 protected override bool HasAdditionalUpdateAccess()
 {
  return true;
 }
}

We now need to add our Persisted properties. As stated earlier I’m storing 3 different pieces of data. A database string, a URL to a SearchPage, and the URL to the Google Maps.

[Persisted]
 private string databaseString;
 public string DatabaseString
 {
  get { return databaseString; }
  set { databaseString = value; }
 }
[Persisted]
 private string searchPage;

 public string SearchPage
 {
   get { return searchPage; }
   set { searchPage = value; }
 }

[Persisted]
private string googleMapUrl;

public string GoogleMapsUrl
{
   get { return googleMapUrl; }
   set { googleMapUrl = value; }
}

The last thing we are going to do with this class is add a public static class to get the values, if its unable to find the value then it creates a new one.

public static PersistedCannonFodderSettings Settings
{
 get
 {
    SPPersistedObject parent = SPFarm.Local;
    var obj = parent.GetChild<PersistedCannonFodderSettings>(PROPERTY_NAME);
    if (obj == null)
    {
      SPContext.Current.Web.AllowUnsafeUpdates = true;
      obj = new PersistedCannonFodderSettings(parent);
      obj.Update(true);
      SPContext.Current.Web.AllowUnsafeUpdates = false;
     }
    return obj;
  }
}

Now in Central Administration we need to add a page so that the Farm administrator can update the settings. Add a new application page to the project, but map it to the SharePoint ADMIN folder. I’ve called my page Cann0nF0dderSettings.aspx.

As this is an Admin page, please remember to update your Application Page MastPageFile to point to “~/_admin/admin.master”. In the PlaceHolderMain content PlaceHolder add the following.

<asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server">
<div class="s4-notdlg">
    <asp:Panel runat="server" ID="cfspPanel" GroupingText="Cannonfodder Persisted Settings" CssClass="panel-wrapper">
        <div class="item">
            <asp:Label ID="lbldbString" runat="server" Text="Database String"></asp:Label>
            <asp:TextBox ID="txtdbString" runat="server" />
        </div>
        <div class="item">
            <asp:Label ID="lblSearchPage" runat="server" Text="Search Page Url"></asp:Label>
            <asp:TextBox ID="txtSearchPage" runat="server" />
        </div>
        <div class="item">
            <asp:Label ID="lblGoogleMapsUrl" runat="server" Text="Google Maps Url"></asp:Label>
            <asp:TextBox ID="txtGoogleMapsUrl" runat="server" />
        </div>
        <div class="buttons">
            <asp:Button runat="server" ID="btnUpdate" Text="Update" OnClick="btnUpdateButton_Click" />
        </div>
        <div class="result">
            <asp:Label runat="server" ID="lblResult"></asp:Label>
        </div>
    </asp:Panel>
</div>
</asp:Content>

With the corresponding CSS in the PlaceHolderAdditionalPageHead I now have an administration page that looks like the following.

  .panel-wrapper fieldset
  {
       margin-top : 5px;
       margin-bottom : 20px;
       width : 500px;
  }
  .panel-wrapper fieldset legend
  {
      margin-left:10px;
      margin-bottom:10px;
      padding-left:5px;
      padding-right:5px;
  }
  .item
  {
      padding-bottom:10px;
      padding-left:10px;
  }
  .item input
  {
      width:480px;
      margin-right:10px;
  }
  .buttons
  {
      padding-left:10px;
      padding-bottom:10px;
  }
  .result
  {
      margin:5px;
  }

In the code behind for the page we have two methods. The Page_Load method which gets the existing settings and populates the 3 textboxes. The btnUpdateButton_Click button will read in the values from the textboxes and persist them back to the SharePoint database. Remember that this a only an example and I haven’t included any validation on the URL’s or database string check.

protected void Page_Load(object sender, EventArgs e)
{
  if (!Page.IsPostBack)
  {
    var persistedSettings = PersistedCannonFodderSettings.Settings;
       txtdbString.Text = persistedSettings.DatabaseString;
       txtSearchPage.Text = persistedSettings.SearchPage;
       txtGoogleMapsUrl.Text = persistedSettings.GoogleMapsUrl;
   }
 }
public void btnUpdateButton_Click(object sender, EventArgs e)
{
  try
  {
    var persistedSettings = PersistedCannonFodderSettings.Settings;
    persistedSettings.DatabaseString = txtdbString.Text;
    persistedSettings.SearchPage = txtSearchPage.Text;
    persistedSettings.GoogleMapsUrl = txtGoogleMapsUrl.Text;
    persistedSettings.Update(true);
    lblResult.Text = "Settings Updated Successfully";
   }
   catch (Exception ex)
   {
    lblResult.Text = ex.Message;
   }
}

I’m not writing the code for the custom action that will display in the link to the administration page in Central Admin. You can either download the example at the end of this blog and see how I did it in there, or follow my other blog on Adding your own CustomActionGroup to Central Adminstration

The code in the Page_Load is the exact same code you would use within your webpart/application page etc to obtain the values. Just remember to include your .dll and using statement if calling from a different project.

var persistedSettings = PersistedCannonFodderSettings.Settings;
lblDataSourceValue.Text = persistedSettings.DatabaseString;
lblGoogleMapsValue.Text = persistedSettings.GoogleMapsUrl;
lblSearchPageValue.Text = persistedSettings.SearchPage;

Below is a screen shot of a basic webpart that brings back the values. Of course you probably wouldn’t display these values to a user, but use these values within the webpart to perform additional tasks.

So the last part of this blog is showing you how to update your custom SPPersistedObject using Powershell. I’ve written a function so that it can be called. By passing in the name of the Persisted Object, and the values for 3 properties, we first get the object, check if it exists. If it does exist we delete the existing object and then re-create it and adding the properties. Lastly we update the property.

if ( (Get-PSSnapin -Name Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue) -eq $null ) {
  Add-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue
}
function SetupCFPersistedObjects($objectName, $databaseData, $searchURL, $googleMapsUrl)
{
 #Load the assembly
 [System.Reflection.Assembly]::LoadWithPartialName("CFSP.PersistedObjects") | Out-Null
 #Get the farm
 $farm = [Microsoft.SharePoint.Administration.SPFarm]::Local
 #Get the Custom PeristedObject from the farm.
 $existingSettingsObj = $farm.GetObject($objectName, $farm.Id, [CFSP.PersistedObjects.PersistedCannonFodderSettings])
 #if it exists, delete it
 if($existingSettingsObj -ne $null)
 {
  $existingSettingsObj.Delete();
  $existingSettingsObj.Unprovision();
  $existingSettingsObj.Uncache();
 }

 #create a new PersistedCannonFodderSettings Object
 [CFSP.PersistedObjects.PersistedCannonFodderSettings] $settings = New-Object CFSP.PersistedObjects.PersistedCannonFodderSettings ($farm)
 #Insert the new values.
 $settings.DatabaseString = $databaseData
 $settings.SearchPage = $searchURL
 $settings.GoogleMapsUrl = $googleMapsUrl
#Update the persisted Object.
$settings.Update();

Write-Host "Cann0nFodder's Persisted data configured"
}

SetupCFPersistedObjects "CFSP ExternalDatabaseConnection" "Data Source=NewDatabase;Initial Catalog=Cann0nF0dderWorks;Integrated Security=true;" "http://cfsp/sites/Search/ResultsPage.aspx?k=""https://maps.google.co.uk/maps?q="

After running the above powershell, you can now see in our administration page, or within my webpart that the values have been updated.

If you wish to download this project, you can from my SkyDrive here.

Advertisements

One thought on “Creating, Using SPPersistedObjects and creating with PowerShell

Comments are closed.