Wednesday, February 24, 2010

Web entry forms on SharePoint Proof Of Concept

On almost each publishing website some kind of contact form is present. When developing a site with SharePoint one can create forms with:
* Infopath
* Third party provider (for example Kodision)
* A form generator based on a SharePoint List (for example SFG)

Each solution has it's own pro's and con's. Most of the time a solution like SFG suffices and has the advantage that the forms can be maintained by ContentEditors themselves. SFG generates form fields based on the columns of a specific list. Upon submitting the form by a visitor, a new listitem will be added to the list with the values enterd by the visitor.

Unfortunatly when Content Deployment is put in the mix to achieve a seperate content authoring farm and a production farm situation, the form data will be stored on the production farm. Most of the time this farm will not be accessible by ContentEditors (anonymous access only) and thus the form data is inaccessible. It's ofcourse possible to extend the WebApplication, but this probably causes confusion for the ContentEditors and is prone to editting on the wrong farm.

By nature "Form Generators" are not very suitable for implementing exceptions.

As a Proof Of Concept i've written a very simple solution called "UserControlFormSolution" which consists of a simple form processing back-end. The front-end must be a UserControl (ascx) which inherits from the back-end class. On the front-end one can place regular CustomControls (textboxes, checkboxes, uploadcontrols, validationcontrols).

The back-end does a simple transfer of the Input-controls (from the UserControl) to the fields of a specified list by matching their names. So to fill the Title-field of a listiem, the UserControl may contain:



Instead of using the ObjectModel to store the listitem, i chose to use the Lists WebService and thus enable storing data in a remote farm.

The source code is placed on CodePlex here.

But the story does not end here as a UserControl needs to be placed on a page. This would mean that for each form one must create a specific PageLayout and add the UserControl to the main placeholder. To overcome this requirement i´ve used the UserControlHostingWebPartSolution. This solution provides a SmartPart-like webpart with which one can host a UserControl. This solution provides the extra functionality to be able to set properties of the UserControl.



To enable ContentEditors to maintain the form UserControls, the ASCX-files must be made accessible. To achieve this i've used the SharePointHostedUserControlsSolution. This solution enables one to host UserControls from within a document library (/UserControls/) from the SharePoint SiteCollection. The Solution contains a WebApplication Feature to modify the web.config.

At this point your ContentEditors can use SharePoint Designer to modify a form.

Monday, October 5, 2009

ContentTypeUsage Solution

I just finished a solution with which it would be easy to determine where ContentTypes are in use. The solution should work for WSS3 and Moss2007.

In the Solution there is a Site Collection Feature which registers two Custom Actions.

Navigate to a ContentType:





The "View usage" CustomAction should be visible:


The urls will be displayed of the Lists where the ContentType is in use:

This solution is not extensivly tested yet.
https://MRCL.svn.codeplex.com/svn/Wss3/ContentTypeUsageSolution/ContentTypeUsageSolution.wsp

WSPBuilder + Visual Studio 2008

Tuesday, September 15, 2009

GoogleAnalytics Solution

Just finished a WSS3/Moss2007 Solution to provide GoogleAnalytics page-tracking.


The Solution consists of two seperate SiteCollection-features:




This is a snapshot of the configuration page which is accessible via SiteSettings:





The GoogleAnalytics-script will be injected via a DelegateControl which must be present in the head of the MasterPage.


The sourcecode and package is available for download from Codeplex Subversion.

Over here you'll be able to find a blog of Wesley Baker which also has a Google Analytics Solution. I reused some of his ideas for my implementation.

Monday, September 14, 2009

GoogleAJAXLibrariesAPI

Since over a year Google Code is hosting a set of clientside libraries under the name Google AJAX Libraries API. For easy usage in Sharepoint (WSS3 / Moss2007) i´ve wrapped the API's into seperate SiteCollection Features.



For the features to work, the Masterpages must be prepped by adding the following DelegateControl to the head:



When all Features of this Solution are activated, the following script will be rendered in the head:


The sourcecode and the package of the Solution can be downloaded from here. The solution is built using Visual Studio 2008 and WSPBuilder.
Because the scripts are loaded from the Google-servers, websites running under HTTPS might display clientside warnings to the visitor.
At this moment i've selected versions by a general string, as this would enable minor updates of the libraries to automatically be used. Selecting a specific version of a library is easy by changing the version-string in the elements.xml-files.

FieldContentPanel

The FieldContentPanel is a simple derivative of the standard System.Web.UI.WebControls.Panel. The control is extended with the functionality to suppress itself and it's contents depending on the presence of a value for the given field from the current page/listitem.

This control is based on this posting of Andrew Connell. The sourcecode and the ready-to-use WSP is available for download from the MRCL Codeplex Subversion repository.

This is an example of typical usage of the control:


The FieldName-attribute/property defines which field of the current listitem/page which will be checked for an empty value.

The AlwaysShowInEditMode-attribute/property defines if the panel needs to be displayed in edit-mode even if the fieldvalue would be empty. This setting is important when the FieldContentPanel checks the same field as one of it´s children FieldControl(s). Without this setting one would be unable to change the value of a Field after it has been set to empty.

The solution is built using Visual Studio 2008 and WSPBuilder. This version of the control is usable in WSS3 and Moss2007.

The control can be easily be extended to support custom field-types which have specific empty values.

Tuesday, March 10, 2009

Sharepoint Extended Lookup Field

When designing an Sharepoint implementation I started out with OOTB Sharepoint behaviour. I added a LookupField to the list and pointed it to another list. But OOTB all listitems from the referenced list would show up in the dropdown as options. I wanted to be able to control which options would be shown.

I've found an open source fieldcontrol at http://extendedlookup.codeplex.com/ which would be able to do just that by using a view. Unfortunatly it didn't work right-away for me, probably due to some updates from SP-hotfixes. After making some minor changes, i was able to get it working again.

When fiddling with the control I noticed that when a selected item wasn't included in the view it would revert to the first/default-value. Once again i made a minor change, which would make it able to add the selected-item as an option.

Update: just noticed the archived comment at http://extendedlookup.codeplex.com/:
See other projects it looks similar: http://www.codeplex.com/CustomFieldControls http://www.codeplex.com/iLoveSharePoint/Release/ProjectReleases.aspx?ReleaseId=15633

Monday, March 9, 2009

Web Part Audience Targeting RoleProvider-roles through SharePoint Groups

I've managed to spend some days to understand the Audience Targeting functionality of SharePoint Server 2007 (MOSS). I wish to share my conclusions:

* Global Audiences (rule-based, SSP) is OOTB only possible when using SharePoint UserProfiles. I wanted to bind it to an ASP.NET Profile Provider, but that's not possible.

* The Global Audiences (rule-based) functionality is realized directly in-database via stored-procedures.

* It is possible to target (changing visibility) Web Parts to SharePoint Groups.

* If you target a Web Part to a "ASP.NET RoleProvider"-role by adding the role to a Sharepoint Group does not work. (Forms Based Authentication)

* It is possible to override the "TargetTo"-FieldType by replacing the 12\Template\xml\fldtypes_TargetTo.xml file with your own version. This way you are able to hook your own FieldControl into the Audience-functionality.

* Most code which is related to Targeting is hardwired to use "Microsoft.Office.Server.Audience.AudienceManager". It is thus not easy replaceable or extendable

* The ContentByQuery-WebPart is hardwired to use field with ID "{61cbb965-1e04-4273-b658-eedaa662f48d}" as Audience-field.

* The AuthorizationFilter.Editor of Web Parts is buggy. When removing Sharepoint Groups as Target, many strange bugs become apparent.

* It is not possible to manually AudienceFilter the ContentByQuery-WebPart by selecting the "Audience"-field as filter. It gave me an error because it was trying to compare an ntext- with an nvarchar-column.


I wrote the following class to be able to correctly use RoleProvider-roles as Sharepoint-groups for targeting Web Parts. You should register the class as ResourceFilter in the web.config of your SharePoint-site.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
 
using Microsoft.Office.Server.Audience;
using System.Collections;
using System.Globalization;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.SharePoint;
 
namespace AudienceManagerUI
{
    /// <summary>
    /// Replace <RuntimeFilter Assembly="Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Class="Microsoft.Office.Server.Audience.AudienceManager" BuilderURL="audience_chooser.aspx" />
    /// With <RuntimeFilter Assembly="AudienceManagerUI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6cb89fff80058439" Class="AudienceManagerUI.PatchedResourceManager" BuilderURL="audience_chooser.aspx" />
    /// </summary>
    public class PatchedResourceManager : Microsoft.SharePoint.WebPartPages.IRuntimeFilter2
    {
        private Microsoft.Office.Server.Audience.AudienceManager audienceManager = new Microsoft.Office.Server.Audience.AudienceManager();
 
        public ArrayList InitializeStrings(CultureInfo cultureInfO)
        {
            return audienceManager.InitializeStrings(cultureInfO);
        }
 
        public IToolPaneControl GetToolPaneControl()
        {
            return audienceManager.GetToolPaneControl();
        }
 
        public bool IsFilterActive()
        {
            return audienceManager.IsFilterActive();
        }
 
        /// <summary>
        /// Checks that the values in the IsIncludedFilter property are valid before they are saved
        /// </summary>
        /// <param name="PersistedString"></param>
        /// <returns></returns>
        public string ValidateIsIncludedFilter(string PersistedString)
        {
            return audienceManager.ValidateIsIncludedFilter(PersistedString);
        }
 
        public bool CheckRuntimeRender(string isIncludedFilter)
        {
            bool result = audienceManager.CheckRuntimeRender(isIncludedFilter);
            if (result) return result;
            string[] targets = isIncludedFilter.Split(new string[] {";;" }, StringSplitOptions.None);
            if (targets.Length == 3)
            {
                string[] groups = targets[2].Split(new char[] {';'}, StringSplitOptions.RemoveEmptyEntries);
                foreach (string target in groups)
                {
                    try
                    {
                        SPGroup group = SPContext.Current.Site.RootWeb.SiteGroups[target];
                        if (group.ContainsCurrentUser) return true;
                    }
                    catch (SPException)
                    {
                    }
                }
            }
            return false;
        }
    }
}