Building Enterprise Solution Catalogs in SharePoint 2010
Enterprise Solution Catalogs are a great way of delivering solutions to your end users. Find out how to build an Enterprise Solution Catalog for your company by leveraging the extensibility capabilities of the SharePoint 2010 platform.
Extending the SharePoint platform
Probably the greatest strength of the SharePoint 2010 platform is its extensibility capabilities. While SharePoint 2010 contains some standard functionality, it’s the ability of configuring it, customizing it and building on top of it that allows you to get the very most out of SharePoint for your organization.
When building SharePoint solutions there are a number of options you can choose from. One approach is to use nothing more than standard functionality provided with SharePoint and use nothing more than configuration capabilities of those standard components. Another approach is to use the Client Object Model (Silverlight or ECMA script) to interact with SharePoint data. Finally you can use the server API to get the richest integration with the SharePoint platform. No matter your choice, once done, you would very likely package your solution to make it easy to install and reuse for your end users. An Enterprise Solution Catalog seems like a great way to deliver such solutions to end user so that they can install them on their sites.
Enterprise Solution Catalog – the requirements
An Enterprise Solution Catalog is a platform for showcasing and distributing SharePoint solutions within your organization. As opposite to a public marketplace, an Enterprise Solution Catalog is internal only meaning that you can ensure that your intellectual property will not leave the organization and yet have all the different departments and teams benefit of each others work.
From the publishers perspective an Enterprise Solution Catalog should at least support a way to upload solutions alongside with some information about them such as title, description, purpose, etc. Organization might want to have some process in place that would allow them to review the uploaded solutions and control how they appear in the Solution Catalog. Finally end users should be given the ability to easily discover available solution and install them without too much work.
Catalog? It’s already there!
Given those requirements we could build a custom solution that would become an Enterprise Solution Catalog. Did you know however, that SharePoint 2010 provides you with an extensible mechanism for building your own Enterprise Solution Store?
Although it’s not that well known or documented you might have noticed it whenever you create a new List or Site – the Office.com link in the Create dialog window.
Although clicking the Office.com doesn’t do much at the moment, it is a good example that shows how you can extend the Create dialog and add your own Enterprise Solution Store to it.
Anatomy of a solution provider
The Create dialog in SharePoint 2010 is built around a provider model: every link is an implementation of a Gallery Provider that is responsible for returning Gallery Items (solutions).
How it works: retrieving available providers
Every time the Create dialog window opens the Gallery is loaded. The Gallery is driven by the 14\TEMPLATE\LAYOUTS\AddGallery.aspx Application Page and the Gallery Silverlight application is located in the 14\TEMPLATE\LAYOUTS\AddGallery.xap file. The Application Page contains the AddGalleryWebPart Web Part which is the backbone of the Gallery mechanism and facilitates communication between the UI and Gallery Providers.
One of the first calls made after opening the Create dialog window is retrieving available Gallery Providers which in this case is nothing more than an assembly located in the 14\TEMPLATE\LAYOUTS\AddGalleryProviders folder. Out of the box you can find there the AddGallery.OfficeOnlineProvider.dll assembly which contains the implementation of the Office.com Gallery Provider and is a great resource to see how a Gallery Provider works.
Important: Gallery Providers are Farm-wide so you cannot install them for specific Web Applications/Site Collections only.
After available Gallery Providers are enumerated, their assemblies are streamed to the Gallery application and loaded.
How it work: loading a Gallery Provider
After the assembly with Gallery Provider has been loaded in the Gallery application the available Gallery Providers are instantiated. Each Gallery Provider is a class that inherits from the Microsoft.SharePoint.Solutions.AddGallery.Provider.ClientFilteredProvider<T> base class. That class can be found in the AddGallery.Provider assembly located inside the AddGallery.xap file.
After the Gallery application has discovered Gallery Providers’ classes it instantiates them and adds them to the UI of the Gallery application.
How it works: configuration
Right after a Gallery Provider has been initialized, the Gallery application checks if configuration for that provider is available. A configuration in this case is a text file which must match the name of the Gallery Provider assembly plus .config for example: given we had a Gallery Provider assembly Mavention.SharePoint.EnterpriseSolutionStore.dll then a valid configuration file would have to be called Mavention.SharePoint.EnterpriseSolutionStore.dll.config in order to be loaded by the Gallery engine.
You are free in how you define the configuration file. Its contents, if available, are streamed together with the assembly and are made available in your Gallery Provider via the ClientFilteredProvider<T>.Configuration property. It’s worth noting that the configuration is available after the Gallery Provider has been registered with the Gallery application UI so you cannot for example configure its Title that is displayed in the Gallery application.
How it works: providing Gallery Items
The last thing that a Gallery Provider is responsible for is providing the Gallery application with the list of available Gallery Items (solutions). When building a Gallery Provider you have to provide the name of the class that will be used for defining Gallery Items for your Gallery Provider. When implementing a Gallery Item class you have to at minimum inherit from the Microsoft.SharePoint.Solutions.AddGallery.Provider.GalleryItem abstract class which you can then optionally enrich with your own properties.
Building a minimal Enterprise Solution Catalog
To get better understanding of how everything works, let’s take a look at building a minimal Gallery Provider.
Preparing the solution
Important: As you might have noticed, the Gallery is a Silverlight application so in order to ensure that your Gallery Provider will be able to run in context of a Silverlight application you should choose the Silverlight Class Library Project Template as a starting point to ensure that your assembly will have valid assemblies references.
To build a Gallery Provider we will need two projects: one – a Silverlight Class Library project, to implement the Gallery Provider and one – a SharePoint Project, to deploy the Gallery Provider to SharePoint. Optionally, if you would like to use configuration with you Gallery Provider, you should add another SharePoint Project that would deploy the .config file to the 14\TEMPLATE\LAYOUTS\AddGalleryProviders folder. Keeping the configuration separate from the Gallery Provider allows you to have different configuration files for different environments such as development, UAT and production.
The following screenshot presents the project structure used for building a minimal Gallery Provider that uses configuration:
Tip: Because neither the Installer nor the Configuration projects contain any code you can set their Include Assembly In Package properties to False not to include unnecessary assemblies in the WSP.
Configuring Gallery Provider deployment
Before we start building the Gallery Provider, let’s configure the deployment options so that both the Gallery Provider and its configuration will get deployed to the right location in the SharePoint Root Folder.
To the Installer project add an Empty Element called GalleryProvider. Remove the Elements.xml file and in the SPI’s Properties click the ellipsis next to the Project Output References property. Add a new Project Output Reference. Set Project Name to the Mavention.SharePoint.MinimalGalleryProvider project (Silverlight Class Library project), Deployment Type to TemplateFile and finally Deployment Path to LAYOUTS\AddGalleryProviders. The following screenshot shows the complete configuration.
After configuring the Project Output References add the GalleryProvider SPI to the Package. To verify that everything has been configured correctly Package the Installer project. By browsing through the contents of the hidden pkg folder you can verify that the Gallery Provider assembly will end up in the right location.
Configuring Gallery Provider Configuration deployment
The next step is to ensure that the configuration file for our Gallery Provider will be deployed to the right location. For that add a new SharePoint Mapped Folder to the Configuration project and in the tree view select the TEMPLATE\LAYOUTS\AddGalleryProviders folder.
Next add to it the configuration file which should be called Mavention. SharePoint.MinimalGalleryProvider.dll.config.
Getting prerequisites for building a Gallery Provider
The last thing that we need to configure before we can start building our minimal Gallery Provider is to get a reference to the AddGallery.Provider base assembly. For that go the 14\TEMPLATE\LAYOUTS folder and copy the AddGallery.xap file. Change the extension to .zip and extract the contents. From there copy the AddGallery.Provider.dll file and paste is somewhere in your solution structure. Next, in the Gallery Provider project (Silverlight Class Library project) add a new Assembly Reference and lookup the AddGallery.Provider assembly. After it has been added as a reference set the Copy Local property to False.
Tip: To simplify deploying the Gallery Provider you can set the Post-Build event of the Silverlight Class Library project to copy ”$(TargetPath)” “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\AddGalleryProviders” /Y
Tip: The Gallery Provider is Silverlight code so if you want to debug it you have to attach the debugger to the iexplore.exe process rather than the w3wp.exe process that runs SharePoint code.
This concludes the preparations and allows us to start building the minimal Gallery Provider.
Building the minimal Gallery Provider
At minimal a Gallery Provider consists of two classes: a Gallery Item that inherits from the Microsoft.SharePoint.Solutions.AddGallery.Provider.GalleryItem class and the Gallery Provider itself which inherits from the Microsoft.SharePoint.Solutions.AddGallery.Provider.ClientFilteredProvider<T> class.
Whenever you define the Gallery Item class you can simply inherit from the base class. No extra properties have to be defined and no methods have to be defined. The following code snippet presents the minimal code for a Gallery Item.
using Microsoft.SharePoint.Solutions.AddGallery.Provider;
namespace Mavention.SharePoint.MinimalGalleryProvider {
public class MinimalGalleryProviderGalleryItem : GalleryItem {
}
}
Next we can continue with building the minimal Gallery Provider. Whenever you inherit from the ClientFilteredProvider<T> generic class you have to pass the type of the Gallery Item class that the Gallery Provider will use. In our case this is the MinimalGalleryProviderGalleryItem class. Additionally you have to define two methods: BeginGetDistinctFilterValues and BeginGetItems. The BeginGetDistinctFilterValues method can be used if you want to define additional filters for your provider. We will skip it for now and will use the default implementation. The BeginGetItems method is the core of every Gallery Provider and contains the logic responsible for retrieving Gallery Items (solutions).
The following code snippet presents the base code for our minimal Gallery Provider.
using System.Collections.Generic;
using System.Globalization;
using Microsoft.SharePoint.Solutions.AddGallery.Provider;
namespace Mavention.SharePoint.MinimalGalleryProvider {
public class MinimalGalleryProvider : ClientFilteredProvider<MinimalGalleryProviderGalleryItem> {
IList<MinimalGalleryProviderGalleryItem> items;
public MinimalGalleryProvider() {
Title = "Mavention Solution Store";
}
public override void BeginGetDistinctFilterValues(CultureInfo language, ItemAttribute itemAttribute, object asyncState) {
IList<FilterValue> filterValues = ProcessFilterValues(items, itemAttribute);
OnGetDistinctFilterValuesCompleted(new GetDistinctFilterValuesCompletedEventArgs {
AsyncState = null,
Error = null,
ItemAttribute = itemAttribute,
DistinctFilterValues = filterValues
});
}
public override void BeginGetItems(CultureInfo language, string searchText, FilterCollection filters, SortOrderCollection sortOrders, int firstItemIndex, int lastItemIndex, object asyncState) {
}
}
}
Next to defining the required methods in the constructor of the Gallery Provider on line 10 we have defined the title of our Gallery Provider. As discussed previously the title is being used immediately after instantiating the Gallery Provider so it’s important to define it in the Gallery Provider constructor.
If we deployed the Installer solution now this is what you should see after opening the Create dialog window:
Although our Gallery Provider doesn’t display any items at this moment, it proves that so far we have done everything correctly.
Loading Gallery Items
From the Gallery perspective there is no restrictions with regard to what you want to use as the store for your solutions. You could for example use something as simple as a SharePoint List but you could as well use a cloud-based solution that is running on Azure. This is why the exact implementation logic for the BeginGetItems method will differ based on where you store the solutions. So instead of getting a complete backend implementation, let’s focus on what the capabilities of the Gallery are and how you can present your solutions.
Let’s start with defining a few items in our Gallery Provider by changing the code of the BeginGetItems method as shown in the following snippet:
public override void BeginGetItems(CultureInfo language, string searchText, FilterCollection filters, SortOrderCollection sortOrders, int firstItemIndex, int lastItemIndex, object asyncState) {
items = new List<MinimalGalleryProviderGalleryItem> {
new MinimalGalleryProviderGalleryItem {
DisplayName = "Mavention Solution 1",
Description = "This is Mavention Solution #1",
ImageUrl = "/_layouts/images/ltpp.png",
Categories = new List<string> { "Category 1", "Category 2" }
},
new MinimalGalleryProviderGalleryItem {
DisplayName = "Mavention Solution 2",
Description = "This is Mavention Solution #2",
ImageUrl = "http://mavention/_layouts/images/ltworksp.png",
Categories = new List<string> { "Category 1", "Category 3" }
}
};
GalleryItemCollection galleryItems = ProcessItems(items, searchText, filters, sortOrders, firstItemIndex, lastItemIndex);
OnGetItemsCompleted(new GetItemsCompletedEventArgs {
AsyncState = asyncState,
Error = null,
Items = galleryItems
});
}
If you reload the Gallery you will see our two items displayed on the screen:
For every item the display name (1), a description (2), an image (3) and the categories (4) will be displayed. All properties except the display name are optional. Additionally, based on all unique categories the categories refiner will be filled (5).
Although our Gallery Items are now displayed there is no link to the underlying solutions yet and if you click the Create button you will simply get redirected to the welcome page of your site.
Linking Gallery Items to solutions
The underlying GalleryItem class, that is the base for every Gallery Item, contains a number of properties that allow you to define what the particular Gallery Item exactly is and how it should be used. You can define the type of the Gallery Item by using the GalleryItem.Type property. In this scenario we will focus on using the Solution type only but you should definitely explore other item types.
items = new List<MinimalGalleryProviderGalleryItem> {
new MinimalGalleryProviderGalleryItem {
DisplayName = "Mavention Solution 1",
Description = "This is Mavention Solution #1",
ImageUrl = "/_layouts/images/ltpp.png",
Categories = new List<string> { "Category 1", "Category 2" },
Type = ItemType.Solution
},
new MinimalGalleryProviderGalleryItem {
DisplayName = "Mavention Solution 2",
Description = "This is Mavention Solution #2",
ImageUrl = "http://mavention/_layouts/images/ltworksp.png",
Categories = new List<string> { "Category 1", "Category 3" },
Type = ItemType.Solution
}
};
When working with solutions one of the most important properties that you should define for every Gallery Item is the GalleryItem.CodeType property which defines what type of solution the particular Gallery Item is. Depending on the value of this property different requirements have to be fulfilled by the user for him to be able to install the solution. The CodeType property has three values: NoCode, PTC and FT. Although there is no documentation on the exact meaning of those values you can think of them in the following terms:
- NoCode: the solution package contains no code and in order to see the solution the user has to have the following SharePoint permissions: ManageSubWebs, ManageWeb and AddAndCustomizePages
- PTC (Partially Trusted Code): in order to see the solution in the gallery the user must be a Site Admin (SPUser.IsSiteAdmin equals true)
- FT (Full Trust Solutions): not supported at this moment and never displayed in the Gallery
items = new List<MinimalGalleryProviderGalleryItem> {
new MinimalGalleryProviderGalleryItem {
DisplayName = "Mavention Solution 1",
Description = "This is Mavention Solution #1",
ImageUrl = "/_layouts/images/ltpp.png",
Categories = new List<string> { "Category 1", "Category 2" },
Type = ItemType.Solution,
CodeType = SolutionCodeType.NoCode
},
new MinimalGalleryProviderGalleryItem {
DisplayName = "Mavention Solution 2",
Description = "This is Mavention Solution #2",
ImageUrl = "http://mavention/_layouts/images/ltworksp.png",
Categories = new List<string> { "Category 1", "Category 3" },
Type = ItemType.Solution,
CodeType = SolutionCodeType.PTC
}
};
The CodeType property works in combination with the GalleryItem.ShowOnlyIfUserHasPermissions property which is set to true by default. If you would like a particular Gallery Item to be always visible you could set its ShowOnlyIfUserHasPermissions property to false.
After setting the type of solution the next step is to make the Gallery Item point to the physical WSP file. By default the Action for each Gallery Item is set to ActionType.Link which means that after clicking the Create button the Gallery will navigate to the URL specified using the GalleryItem.Link property. If you want your solutions to be downloaded and installed instead you have to set the GalleryItem.Action property to ActionType.ForDownload and provide the URL of the WSP using the GalleryItem.URL property. There is no restriction as to where this file is located as long as it can be accessed by the specified URL.
items = new List<MinimalGalleryProviderGalleryItem> {
new MinimalGalleryProviderGalleryItem {
DisplayName = "Mavention Solution 1",
Description = "This is Mavention Solution #1",
ImageUrl = "/_layouts/images/ltpp.png",
Categories = new List<string> { "Category 1", "Category 2" },
Type = ItemType.Solution,
CodeType = SolutionCodeType.NoCode,
Action = ActionType.ForDownload,
Url = "/SolutionCatalog/MaventionSolution1.wsp"
},
new MinimalGalleryProviderGalleryItem {
DisplayName = "Mavention Solution 2",
Description = "This is Mavention Solution #2",
ImageUrl = "http://mavention/_layouts/images/ltworksp.png",
Categories = new List<string> { "Category 1", "Category 3" },
Type = ItemType.Solution,
CodeType = SolutionCodeType.PTC,
Action = ActionType.ForDownload,
Url = "http://mavention/SolutionCatalog/MaventionSolution2.wsp"
}
};
Notice that the Create button automatically changed to Download and if you click it the solution should be automatically downloaded and installed, given you have sufficient permissions on the current site.
Configuring additional settings
The Gallery has a few more options that can be configured that allow you to control how you Gallery Items are displayed.
Dealing with solution requirements
Often when building solutions we depend on some other resources: this can be either a specific SharePoint version, license, patch level, but it can also be a specific Feature that must be installed activated. For every Gallery Item we can define which dependencies it has on other resources.
You can define requirements for a Gallery Item using the GalleryItem.RequiredResources property. Defining requirements is quite complex. First you have to create an instance of the SharePointRequirementCollection class. Using its Requirements property you have to then define an array of SharePointRequirement objects – one for every dependency of your solution. When defining a requirement you can choose its type (which is an enumeration of fixed values) but it’s the CompatibleResources property that it’s challenging to define. For every requirement there is at least one compatible resource which fulfills that requirement. Requirements are compared using their names (the SharePointRequirement.Name property) and for requirements of type Feature and ProductPatch also the version number (the SharePointRequirement.ResourceVersion property) is taken into account.
Probably the easiest way of configuring required resources for your solution and ensuring that you have the right values is to get the current configuration that the Gallery uses for comparisons. The best way to do this is to use Fiddler to explore requests that are being executed after opening the Gallery. One of those requests (1) will have the request body set to Task=GetAdditionalConfig&CurrentWeb=%2F (2). If you zoom in on the response HTML and look for the <AddGalleryResponse> string you will find the XML containing all of the configuration for the current site (3).
Configuring requirements
With that information you could for example define that your solution can be installed only when the Publishing Feature is activated on the current Web:
new MinimalGalleryProviderGalleryItem {
DisplayName = "Mavention Solution 1",
Description = "This is Mavention Solution #1",
ImageUrl = "/_layouts/images/ltpp.png",
Categories = new List<string> { "Category 1", "Category 2" },
Type = ItemType.Solution,
CodeType = SolutionCodeType.NoCode,
Action = ActionType.ForDownload,
Url = "/SolutionCatalog/MaventionSolution1.wsp",
RequiredResources = new SharePointRequirementCollection {
Requirements = new SharePointRequirement[] {
new SharePointRequirement {
CompatibleResources = new SharePointResource[] {
new SharePointResource {
ResourceType = SharePointResourceType.Feature,
Name = "94C94CA6-B32F-4DA9-A9E3-1F3D343D7ECB",
DisplayName = "SharePoint Server Publishing",
MissingMessage = "SharePoint Server Publishing Site Feature",
ResourceVersion = new System.Version("14.0.0.0")
}
}
}
}
}
}
If you would then browse to the Gallery on a Web that doesn’t have the Publishing Feature activated you would see an error message:
and after clicking on that error message you would get an overview of all missing requirements:
Requesting a requirements check
When working with requirements there is one thing you should take into account. By default the Gallery doesn’t run the requirements check and you have to do it yourself. The good news is that the Gallery provides you with a method to run the check which is stored in the CheckItemRequirementsMethod property of your Gallery Provider. The downside is that it accepts the collection of Gallery Items as a parameter of IList<GalleryItem> type. Because you cannot directly cast IList<YourGalleryItem> to the type required by the check method you have two options. You can either create another variable, copy all items there, run the check and then add all Missing Requirements to the GalleryItem.RequiredResources.MissingRequirements property or when implementing the Gallery Provider you could inherit from the ClientFilteredProvider<GalleryItem> class and specify the GalleryItem class as the class for Gallery Item. With that you can still use your own Gallery Item class to define your items but this allows you to make use of the standard check method. Here is the sample code that shows this scenario:
using System.Collections.Generic;
using System.Globalization;
using Microsoft.SharePoint.Solutions.AddGallery.Provider;
namespace Mavention.SharePoint.MinimalGalleryProvider {
public class MinimalGalleryProvider : ClientFilteredProvider<GalleryItem> {
IList<GalleryItem> items;
public MinimalGalleryProvider() {
Title = "Mavention Solution Store";
}
public override void BeginGetDistinctFilterValues(CultureInfo language, ItemAttribute itemAttribute, object asyncState) {
IList<FilterValue> filterValues = ProcessFilterValues(items, itemAttribute);
OnGetDistinctFilterValuesCompleted(new GetDistinctFilterValuesCompletedEventArgs {
AsyncState = null,
Error = null,
ItemAttribute = itemAttribute,
DistinctFilterValues = filterValues
});
}
public override void BeginGetItems(CultureInfo language, string searchText, FilterCollection filters, SortOrderCollection sortOrders, int firstItemIndex, int lastItemIndex, object asyncState) {
items = new List<GalleryItem> {
new MinimalGalleryProviderGalleryItem {
DisplayName = "Mavention Solution 1",
Description = "This is Mavention Solution #1",
ImageUrl = "/_layouts/images/ltpp.png",
Categories = new List<string> { "Category 1", "Category 2" },
Type = ItemType.Solution,
CodeType = SolutionCodeType.NoCode,
Action = ActionType.ForDownload,
Url = "/SolutionCatalog/MaventionSolution1.wsp",
RequiredResources = new SharePointRequirementCollection {
Requirements = new SharePointRequirement[] {
new SharePointRequirement {
CompatibleResources = new SharePointResource[] {
new SharePointResource {
ResourceType = SharePointResourceType.Feature,
Name = "94C94CA6-B32F-4DA9-A9E3-1F3D343D7ECB",
DisplayName = "SharePoint Server Publishing",
MissingMessage = "SharePoint Server Publishing Site Feature",
ResourceVersion = new System.Version("14.0.0.0")
}
}
}
}
}
},
new MinimalGalleryProviderGalleryItem {
DisplayName = "Mavention Solution 2",
Description = "This is Mavention Solution #2",
ImageUrl = "http://mavention/_layouts/images/ltworksp.png",
Categories = new List<string> { "Category 1", "Category 3" },
Type = ItemType.Solution,
CodeType = SolutionCodeType.PTC,
Action = ActionType.ForDownload,
Url = "http://mavention/SolutionCatalog/MaventionSolution2.wsp",
}
};
CheckItemRequirementsMethod(items);
GalleryItemCollection galleryItems = ProcessItems(items, searchText, filters, sortOrders, firstItemIndex, lastItemIndex);
OnGetItemsCompleted(new GetItemsCompletedEventArgs {
AsyncState = asyncState,
Error = null,
Items = galleryItems
});
}
}
}
Defining ratings
One more thing that the Gallery allows you to do to improve the discovery of solutions is to specify star rating for every item. You can do this using the GalleryItem.FiveStarRating property. When setting it you also have to set the GalleryItem.HasFiveStartRating property to true and the GalleryItem.TotalRatingVotes property to the number of votes, eg.
new MinimalGalleryProviderGalleryItem {
DisplayName = "Mavention Solution 2",
Description = "This is Mavention Solution #2",
ImageUrl = "http://mavention/_layouts/images/ltworksp.png",
Categories = new List<string> { "Category 1", "Category 3" },
Type = ItemType.Solution,
CodeType = SolutionCodeType.PTC,
Action = ActionType.ForDownload,
Url = "http://mavention/SolutionCatalog/MaventionSolution2.wsp",
FiveStarRating = 4.5,
HasFiveStarRating = true,
TotalRatingVotes = 1
}
Providing your users with a rating mechanism allows you to engage them more, get feedback on the quality of the solutions and discover high value solutions.
Important: The Gallery only displays the rating provided by the Gallery Provider. It’s up to you to allow your users to rate Gallery Items.
Summary
Creating an Enterprise Solution Store allows you to deliver your solutions to your end users in a user-friendly fashion. SharePoint 2010 has the Gallery component that allows you to build your own solution store for your organization. The Gallery capability is a powerful mechanism that allows you not only to present and deliver your solutions but also to manage dependencies on other resources that are required by your solutions.
In the next article I will share with you a simple solution that allows you to build your own Enterprise Solution Store based on a SharePoint List.