Category Archives: sharepoint

How to add custom validators to the SharePoint ListFieldIterator

Requirements

Today I had the requirement to add custom validation logic to a SharePoint edit form. The edit form is being displayed using a ListFieldIterator. The requirement was to make sure the Date field could not be set to a date in the past.

The solution (Other solutions are available and are most probably better!) I came up with involved subclassing ListFieldIterator and attaching ASP.NET validators at run-time.

Challenge 1

How do you attach a ASP.NET validation control at run-time.

  • Attach the validator at the correct stage of the page/control life-cycle. In the case of the ListFieldIterator I wanted to attach it after the control had build it’s control tree. Override CreateChildControls, call base, then attach.
  • Find the FormField associated with the SharePoint field (SPField) you want to validate.I have an extension method that I use to parse the control hierarchy of a ListFieldIterator as follows:

Usage:

FormField formField = listFieldIterator.GetFormField("MyInternalFieldName");

public static class ListFieldIteratorExtensions
{
    public static FormField GetFormField(this ListFieldIterator listFieldIterator, string fieldName)
    {
        return GetFormField(listFieldIterator, GetFormFields(listFieldIterator), fieldName);
    }


    public static FormField GetFormField(this ListFieldIterator listFieldIterator, List<FormField> formFields, string fieldName)
    {
        FormField formField = (from form in formFields
                               where form.FieldName.Equals(fieldName, StringComparison.InvariantCultureIgnoreCase)
                               select form).FirstOrDefault();

        if (formField == null)
        {
            throw new GeneralApplicationException("Could not find form field: " + fieldName);
        }

        return formField;
    }

    public static List<FormField> GetFormFields(this ListFieldIterator listFieldIterator)
    {
        if (listFieldIterator == null)
        {
            return null;
        }

        return FindFieldFormControls(listFieldIterator);
    }        

    private static List<FormField> FindFieldFormControls(System.Web.UI.Control root)
    {
        List<FormField> baseFieldControls = new List<FormField>();

        foreach (System.Web.UI.Control control in root.Controls)
        {
            if (control is FormField && control.Visible)
            {
                FormField formField = control as FormField;
                if (formField.Field.FieldValueType == typeof(DateTime))
                {
                    HandleDateField(formField);
                }

                baseFieldControls.Add(formField);
            }
            else
            {
                baseFieldControls.AddRange(FindFieldFormControls(control));
            }
        }

        return baseFieldControls;
    }

    private static void HandleDateField(FormField formField)
    {
        if (formField.ControlMode == SPControlMode.Display)
        {
            return;
        }

        Control dateFieldControl = formField.Controls[0];
        if (dateFieldControl.Controls.Count > 0)
        {
            DateTimeControl dateTimeControl = (DateTimeControl) dateFieldControl.Controls[0].Controls[1];
            TextBox dateTimeTextBox = dateTimeControl.Controls[0] as TextBox;
            if (dateTimeTextBox != null)
            {
                if (!string.IsNullOrEmpty(dateTimeTextBox.Text))
                {
                    formField.Value = DateTime.Parse(dateTimeTextBox.Text, CultureInfo.CurrentCulture);
                }
            } 
        }
    }
}
  • Find the Control that is rendered by the FieldControl.Field.FieldRenderingControl. In my specific case a DateTimeField will render a DateTimeControl. Now that we have the form field we grab the rendering control:

Usage:

Control renderedControl = GetControl(formField);

private static Control GetControl(FieldMetadata formField)
{
    return formField.FindControlRecursive(x => x.GetType() == GetChildControlBasedOnFieldType(formField.Field.FieldRenderingControl));
}

private static Type GetChildControlBasedOnFieldType(object field)
{
    if (field is TextField)
    {
        return typeof(TextBox);
    }

    if (field is DropDownChoiceField)
    {
        return typeof(DropDownList);
    }

    if (field is DateTimeField)
    {
        return typeof (DateTimeControl);
    }

    return null;
}

public static Control FindControlRecursive(this Control control, Func<Control, bool> evaluate)
{
    if (evaluate.Invoke(control))
    {
        return control;
    }

    foreach (Control childControl in control.Controls)
    {
        Control foundControl = FindControlRecursive(childControl, evaluate);
        if (foundControl != null)
        {
            return foundControl;
        }
    }

    return null;
} 

  • Now we have found the control we want to validate, we can add the ASP.NET to it’s parent’s control collection

renderedControl.Parent.Controls.AddAfter(control, validator as Control);

Uses another little extension method:

public static void AddAfter(this ControlCollection collection, Control after, Control control)
{
    int indexFound = -1;
    int currentIndex = 0;
    foreach (Control controlToEvaluate in collection)
    {
        if (controlToEvaluate == after)
        {
            indexFound = currentIndex;
            break;
        }

        currentIndex = currentIndex + 1;
    }

    if (indexFound == -1)
    {
        throw new ArgumentOutOfRangeException("control", "Control not found");
    }

    collection.AddAt(indexFound + 1, control);
}

Tagged ,

How do you update the ‘Author’ or ‘Created by’ and ‘Editor’ or ‘Modified By / Last Modified’ fields of a list item (SPListItem)

Sometimes it’s useful to overwrite the created by and last modified by fields and get rid of that pesky ‘System Account’ !

Created By

The internal field name for the person who created a list item is ‘Author’ use SPBuiltInFieldId.Author to access this field. The display name for this field is ‘Created By’ it can be seen in the UI circled below:

SPListItem CreatedBy Author

Last modified

The internal field name for the person who created a list item is ‘Editor’ use SPBuiltInFieldId.Editor to access this field. The display name for this field is ‘Modified By’ it can be seen in the UI circled below:

SPListItem ModifiedBy Editor

Updating Created By, Modified By

The trick here is to call SPListItem.UpdateOverwriteVersion() instead of SPListItem.Update()

SPListItem item = list.Items[0];
item[SPBuiltInFieldId.Author] = "1;#Edward Wilde";
item[SPBuiltInFieldId.Editor] = "1;#Edward Wilde";
copiedItem.UpdateOverwriteVersion();
Tagged , ,

InfoPath forms with workflow: ‘The form has been closed’ or Exception occurred during request processing. (User: {Domain}\{UserName}, Form Name: , IP: , Request: http://localhost/_layouts/CstWrkflIP.aspx?List={ID}, Form ID: , Type: InfoPathLocalizedException, Exception Message: The specified form cannot be found.)

Okay so today’s error message, totally my fault: was

Exception occurred during request processing. (User: DOMAIN\xx, Form Name: , IP: , Request: http://localhost/_layouts/CstWrkflIP.aspx?List={ID}, Form ID: , Type: InfoPathLocalizedException, Exception Message: The specified form cannot be found.)

Make sure that:

  • Your feature file has the RegisterFroms property correctly pointing to a folder in your solution package that contains your infopath forms

    WindowClipping (23)
    In my example all my info paths forms are contained in a sub directory called ‘Forms’ relative to feature file:
    WindowClipping (24)

  • Once the feature has been installed using stsadm –o install feature check that the forms have been placed in the InfoPath template library.

    Central Administration –> Application Management –> Manage Form Templates:

    WindowClipping (25)

As always, your mileage my vary. If this doesn’t help you could try some of the tips on Lucy’s blog: http://blogs.tamtam.nl/lucy/2007/06/08/WorkflowErrors.aspx

Tagged ,

Cannot change the lookup list of the lookup field.

Ever get this error message

SPException “Cannot change the lookup list of the lookup field.”

Once a lookup field (SPFieldLookup) has had it’s LookupList and LookupWebId set you can’t change them.

There is a good reason for this if your lookup field is used in multiple lists or already has data entered, you would effectively break the referential integrity of the data. So what I’m showing here as a work-around to this error message should only be used if you are *sure* no lists reference the lookup column or no lists have any data using this column

Okay with the health warning out of the way the work-around is simply to update the Schema property to overwrite the intervalues for List & WebId

I created a handy extension method for dealing with the string manipulation:

Usage:

SPFieldLookup field = (SPFieldLookup)site.RootWeb.Fields[MyId];
field.UpdateLookupReferences(newWeb, newList);

Implementation:

public static class SPFieldLookupExtensions
{
    public static void UpdateLookupReferences(this SPFieldLookup lookupField, SPWeb web, SPList list)
    {
        if (string.IsNullOrEmpty(lookupField.LookupList))
        {
            lookupField.LookupWebId = web.ID;
            lookupField.LookupList = list.ID.ToString();
        }
        else
        {
            lookupField.SchemaXml =
                lookupField.SchemaXml
                    .ReplaceXmlAttributeValue("List", list.ID.ToString())
                    .ReplaceXmlAttributeValue("WebId", web.ID.ToString());
        }

        lookupField.Update(true);
    }
}

public static string ReplaceXmlAttributeValue(this string xml, string attributeName, string value)
{
    if (string.IsNullOrEmpty(xml))
    {
        throw new ArgumentNullException("xml");
    }

    if (string.IsNullOrEmpty(value))
    {
        throw new ArgumentNullException("value");
    }


    int indexOfAttributeName = xml.IndexOf(attributeName, StringComparison.CurrentCultureIgnoreCase);
    if (indexOfAttributeName == -1)
    {
        throw new ArgumentOutOfRangeException("attributeName", string.Format("Attribute {0} not found in source xml", attributeName));
    }

    int indexOfAttibuteValueBegin = xml.IndexOf('"', indexOfAttributeName);
    int indexOfAttributeValueEnd = xml.IndexOf('"', indexOfAttibuteValueBegin + 1);

    return xml.Substring(0, indexOfAttibuteValueBegin + 1) + value + xml.Substring(indexOfAttributeValueEnd);
}
Tagged ,

Engine RunWorkflow: System.Workflow.ComponentModel.Compiler.WorkflowValidationFailedException: The workflow failed validation.

Getting this error message today:

Engine RunWorkflow: System.Workflow.ComponentModel.Compiler.WorkflowValidationFailedException: The workflow failed validation.     at System.Workflow.Runtime.WorkflowDefinitionDispenser.ValidateDefinition(Activity root, Boolean isNewType, ITypeProvider typeProvider)     at System.Workflow.Runtime.WorkflowDefinitionDispenser.LoadRootActivity(Type workflowType, Boolean createDefinition, Boolean initForRuntime)     at System.Workflow.Runtime.WorkflowDefinitionDispenser.GetRootActivity(Type workflowType, Boolean createNew, Boolean initForRuntime)     at System.Workflow.Runtime.WorkflowRuntime.InitializeExecutor(Guid instanceId, CreationContext context, WorkflowExecutor executor, WorkflowInstance workflowInstance)     at System.Workflow.Runtime.WorkflowRuntime.Load(Guid key, CreationContext context, WorkflowInstance workflowInstance)     at System.Workflow.Runtime.WorkflowRuntime.GetWorkflowExecutor(Guid instanceId, CreationContext context)     at System.Workflow.Runtime.WorkflowRuntime.InternalCreateWorkflow(CreationContext context, Guid instanceId)     at System.Workflow.Runtime.WorkflowRuntime.CreateWorkflow(Type workflowType, Dictionary`2 namedArgumentValues, Guid instanceId)     at Microsoft.SharePoint.Workflow.SPWinOeHostServices.Send(SPWinOeWorkflow winoeworkflow, SPWorkflowEvent e)     at Microsoft.SharePoint.Workflow.SPWinOeEngine.RunWorkflow(Guid trackingId, SPWorkflowHostService host, SPWorkflow workflow, Collection`1 events, TimeSpan timeOut)

 

If your workflow uses validation rules it might be:

1) A problem with your validation rules, restart Visual Studio re-compile are any errors picked up?

 

2) Missing the .net 3.5 workflow targets in your project file see: http://blog.hhebnes.no/post/WSPBuilder-Workflow-Failed-On-Start.aspx

Tagged , ,

An error occured while validating or updating the directory service connection. No update was performed.

I got this error whilst trying to get User Profiles to work. Things to check:

 

  1. Domain name is correct
  2. If using custom sources, try creating a new custom source and deleting the old one
Tagged , ,

SharePoint custom workflow activity. “Failed to load toolbox item. It will be removed from toolbox.”

I was getting this error message in Visual Studio 2008 when adding a custom activity to an existing workflow.

 

Solution

This worked for me, your mileage, may and probably will vary.

It was necessary for me to un-gac the workflow project, then the custom activities could be added to the design surface as expected.

Tagged , , , , ,

Safe mode did not start successfully. Could not load file or assembly

Ever get any of these errors?

WindowClipping (17)

The DataSourceID of ‘TopNavigationMenu’ must be the ID of a control of type IHierarchicalDataSource.  A control with ID ‘topSiteMap’ could not be found.   at System.Web.UI.WebControls.HierarchicalDataBoundControl.GetDataSource()
   at System.Web.UI.WebControls.HierarchicalDataBoundControl.ConnectToHierarchicalDataSource()
   at System.Web.UI.WebControls.HierarchicalDataBoundControl.OnLoad(EventArgs e)
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

Troubleshoot issues with Windows SharePoint Services.

 

Server Error in ‘/’ Application.


Parser Error

Description: An error occurred during the parsing of a resource required to service this request. Please review the following specific parse error details and modify your source file appropriately.
Parser Error Message: This page has encountered a critical error. Contact your system administrator if this problem persists.
Source Error:

Line 1:  <%@ Page Inherits="Microsoft.SharePoint.Publishing.TemplateRedirectionPage,Microsoft.SharePoint.Publishing,
Version=12.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" %> 
<%@ Reference VirtualPath="~TemplatePageUrl" %> 
<%@ Reference VirtualPath="~masterurl/custom.master" %>
Line 2:  <html xmlns:mso="urn:schemas-microsoft-com:office:office" 
xmlns:msdt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"><head>
Line 3:  <!--[if gte mso 9]><xml>

Source File: /Pages/Default.aspx    Line: 1


Version Information: Microsoft .NET Framework Version:2.0.50727.3603; ASP.NET Version:2.0.50727.3082

 

Event log error message

Safe mode did not start successfully. Could not load file or assembly ‘MyCompany, Version=1.0.0.0, Culture=neutral, PublicKeyToken=XXXXXXXX’ or one of its dependencies. The system cannot find the file specified.

Solution

In my case the error message from the event log was spot on. I had accidentally removed a MyCompany assembly from the GAC.

 

Other errors can also cause the same problem, whilst debugging this issue I came across another blog post that might be of interest: http://www.dotnetmafia.com/blogs/dotnettipoftheday/archive/2008/09/24/safe-mode-did-not-start-successfully-request-failed.aspx

Tagged ,

Programmatically determine is a feature is activated or even installed

Okay so ever needed to see if a particular web or site feature is activated? Well if it’s activated it will be in the SPWeb.Features SPFeatureCollection or SPSite.Features. If it’s not activated it won’t be. Simple.

To check if a feature is installed examine the feature definitions collection on the SPFarm object, below are a couple of little extension methods that demonstrate this concept.

/// <summary>
/// Defines extensions made to the <see cref="SPWeb"/> class
/// </summary>
[CLSCompliant(false)]
public static class SPWebExtensions
{
    public static bool IsFeatureActivated(this SPWeb web, Guid featureId)
    {
        return web.Features[featureId] != null;
    }

    public static bool IsFeatureInstalled(this SPWeb web, Guid featureId)
    {
        SPFeatureDefinition featureDefinition = SPFarm.Local.FeatureDefinitions[featureId];
        if (featureDefinition == null)
        {
            return false;
        }

        if (featureDefinition.Scope != SPFeatureScope.Web)
        {
            throw new GeneralApplicationException(
                string.Format("Feature with the ID {0} was installed but is not scoped at the web level.", featureId));
        }

        return true;
    }
}
Tagged , , ,

The imported project “C:\Program Files\MSBuild\Microsoft.Office.InfoPath.targets” was not found

If you get this error message, which I did this morning opening up someone else’s SharePoint solution, make sure you have InfoPath installed.

Microsoft Visual Studio

 

Once I installed the InfoPath client application  with .Net Programmability Support, see below, the targets file was installed to the MSBuild directory.

Microsoft Office Enterprise 2007

Tagged , ,