I’m working on a project where I need to create some custom task forms for a workflow. I’ve been looking into a few different ways of doing it and I ended up deciding on using custom aspx pages to do the job. I figured I should share how to do it although it’s not all that hard.
But hang on… Why aspx pages? Why not InfoPath? You’d think using InfoPath would be a no-brainer, considering creating custom forms is it’s entire purpose in life.
<rant>
During our last project we flew a guy in from out of town to help us develop a rather complex workflow. His solution involved using InfoPath for a wide range of customized forms. I had no experience with InfoPath so all the forms development, as well as the workflow itself, was his responsibility. Turns out that this self-proclaimed SharePoint Expert was more of a Front-Ahead Design duct-tape kind of guy.
Deploying the workflow across three environments (development, testing and production) was a complete nightmare. For each form with code-behind we had to edit the InfoPath file manually, then publish it against the environment we were deploying to. Then we had to copy it to the solution folder in the 12 hive or, in the case of one form, upload it to Form Services in Central Administration.
Oh, and the rest of the workflow isn’t contained in a wsp file either, so we have to copy everything else manually to 12/GAC as well.
This time around I did some investigation around using aspx forms. I found out a way to easily wrap the entire thing up in a single wsp file to be deployed using stsadm. One file. One deployment script. And the same wsp can be used on all environments without modifications.
It was a complete no-brainer.
</rant>
Ranting aside, I put together a quick solution to show how to do it.
I started out by creating a new empty WSPBuilder project and added a custom content type. In the custom content type you can specify a custom form to be used by adding the <FormUrl> or <FormTemplate> tags. The former is used for aspx pages, while the latter is used for ascx controls.
Here’s the complete content type Elements.xml file:
<ContentType
ID="0x010025D40E7776DC4A39A6829638F8000380"
Name="FooBar"
Description="A custom item"
Group="Aarebrot.CustomForm">
<FieldRefs>
</FieldRefs>
<XmlDocuments>
<XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url">
<FormUrls xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url">
<Display>_layouts/Aarebrot.CustomForm/DisplayForm.aspx</Display>
<Edit>_layouts/Aarebrot.CustomForm/EditForm.aspx</Edit>
<New>_layouts/Aarebrot.CustomForm/NewForm.aspx</New>
</FormUrls>
</XmlDocument>
</XmlDocuments>
</ContentType>
</Elements>
Nothing too crazy going on here. Notice the <XmlDocuments> and <XmlDocument> tags that I added at the bottom. Inside that I added a <FormUrls> tag, which allowed me to specify the New, Edit and Display forms for this content type. MSDN says these URLs must be relative to the root location of the content type. I pointed them to the layouts folder, which is where I plan to store my forms.
Note that you don’t need to specify all three here. If you only want to customize one of the forms, add only the one that you want to change. SharePoint will use the default form for the others.
Also take note of the namespaces used in these tags. This has to be correct, or it won’t work. If you are using the <FormTemplates> tag instead, you need to be using a different namespace. The two namespaces are very similar so make note of this as it’ll save you some headache.
Note the very similar, but yet different, namespace:
<Display>ListForm</Display>
<Edit>ListForm</Edit>
<New>ListForm</New>
</FormTemplates>
The last step is to add the aspx pages that we want to be using as our forms. I ended up placing these in the layouts folder. The reason for this is that I need to do some code behind for my forms for certain functionality. At first I tried to create a list definition and place the forms inside the list itself (like how most OOTB lists do), but this doesn’t allow you to do code behinds.
If you create your layouts folder and try to add the files, you’ll notice that there’s no option to add an aspx file. I did some cheating here and just added .txt files and renamed them to NewForm.aspx and NewForm.aspx.cs respectively.
I based this form on the forms from Robert Sheltons workflow series. Here’s the NewForm.aspx page I came up with:
Language="C#"
AutoEventWireup="true"
Inherits="Aarebrot.CustomForm.NewForm, Aarebrot.CustomForm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=59d7e669f58098cc"
meta:progid="SharePoint.WebPartPages.Document"%>
<%@ Register
TagPrefix="SharePoint"
Namespace="Microsoft.SharePoint.WebControls"
Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register
TagPrefix="Utilities"
Namespace="Microsoft.SharePoint.Utilities"
Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register
Tagprefix="WebPartPages"
Namespace="Microsoft.SharePoint.WebPartPages"
Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">
<div>
<WebPartPages:WebPartZone runat="server" FrameType="None" ID="Main" Title="loc:Main" Visible="true" />
<p>
You can’t create a new item of this content type.
</p>
<asp:Button ID="CancelButton" onclick="CancelButton_Click" runat="server" Text="Cancel" />
</div>
<SharePoint:FormDigest ID="FormDigest1" runat="server" />
</asp:Content>
I left the aspx relatively empty on purpose because I just want to demonstrate how to show a custom form. I’m telling the user that they can’t create a new item of this content type, and then I provide a cancel button that will take them back to the list they came from.
Notice that rather than specifying a code-behind file, we are using the Inherits attribute to link our aspx file to the class of the code-behind class. The reason we have to do it this way is because the code-behind file will be built into an assembly and deployed to the GAC, rather than being copied into the layouts folder. Thus when you open the page it will throw a file-not-found exception.
By inheriting from the class it will go looking for the assembly instead. Since the assembly is in the GAC it won’t have any problems finding it.
This is the NewForm.aspx.cs file:
{
using System;
using System.Web;
using System.Web.UI;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebControls;
/// <summary>
/// This class is responsible for handling the NewForm page
/// </summary>
public class NewForm : Page
{
#region Variables
/// <summary>
/// The current web we are connected to
/// </summary>
private SPWeb currentWeb;
/// <summary>
/// The list that the page was opened from
/// </summary>
private SPList list;
#endregion
#region Events
/// <summary>
/// The PreInit event
/// </summary>
/// <param name="e">Event arguments</param>
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
this.currentWeb = SPControl.GetContextWeb(Context);
this.MasterPageFile = this.currentWeb.MasterUrl;
}
/// <summary>
/// The event handler for the page loading
/// </summary>
/// <param name="sender">The object that raised the event</param>
/// <param name="e">Event arguments</param>
protected void Page_Load(object sender, EventArgs e)
{
if (this.currentWeb != null)
{
this.list = this.currentWeb.Lists[new Guid(Request.Params["List"])];
}
else
{
this.list = null;
}
}
/// <summary>
/// The event handler for the cancel button click event
/// </summary>
/// <param name="sender">The button that was clicked</param>
/// <param name="e">Event arguments</param>
protected void CancelButton_Click(object sender, EventArgs e)
{
// If we came from a list get the URL
// Otherwise get the url of the current web and we’ll redirect there instead
string url = this.list != null ? this.list.DefaultViewUrl : this.currentWeb.Url;
SPUtility.Redirect(url, SPRedirectFlags.UseSource, HttpContext.Current);
}
#endregion
}
}
We’re not doing anything fancy here either. We have an event handler for the cancel button. When it’s clicked we simply redirect back to the list that opened the page. Nothing fancy, but I wanted to demonstrate that we can in fact put some code in here and have it work.
Because we’re creating aspx pages and some code to go along with them, we also need to add reference to the Microsoft.SharePoint and System.Web namespaces.
I didn’t add any files for the EditForm and DisplayForm even though I added them in the content type because I’m just doing a quick demonstration. Obviously if you specify them in the content type you’ll have to add the appropriate files to the solution.
Next I’ve deployed the solution to my VM as well as created a simple announcement list. In this list I’ve added my content type through the List Settings. Opening up the list and clicking the new button shows the following:
Which loads our custom form when clicked like so:
Finally, a few things to note about doing your own custom forms.
When you are using your own custom forms, you take on all the responsibilities that comes along with it. This means you have to create your own controls to get user input. You also have to do your own validation as well as actually creating or editing the items in the list. This doesn’t happen automagically for you. You need to write the code to do it.
Make sure you handle all of this or you can run into situations where users are able to create/edit items without putting in required data, or worse, putting in invalid data what will break the items/list/site.
On another note, there is a web-part you can insert into the page that is used on the OOTB forms. This allows you to shortcut and get all of that stuff out of the box. This can be useful in cases where you want the form to look the same as the OOTB forms, but you need to add some custom functionality above of below the item editing web-part.
The draw back with using the web part is that you can’t customize it at all. It’s all or nothing. I believe Robert Shelton shows an example of this technique in his workflow series that I mentioned earlier.
Leave a Reply to Uwe Cancel reply