Modifying Sharepoint Application Pages in the RIGHT WAY

So many time we come across scenarios where we need to modify the functionality Sharepoint Application Pages (for those who are new to sharepoint Application Pages are physical asp.net pages residing the Layouts folder)

Microsoft recommendation is not to modify these as the modifications can get overwritten in the next service pack or a patch. So what do you do when you want to modify the functionality of a Sharepoint Application Page. For example we had a scenario where we wanted to prevent users from entering 'NT Authority\All Authenticated Users' in the "People and Groups". This is the "aclinv.aspx" application page.

One of the solution is to make the copy of a page and then modify it. It looks good at first but with a bit more of investigation one will realize that it has potential for more bugs because it means rewriting functionality that has already existed and is certainly well tested. The other solution i am going to present here will demonstrate the power and flexibility of the asp.net framework.  This solution involves creating a HTTPModule which will modify the control tree of the page to achieve the required functionality.

Lets look at the scenario i mentioned above where i want to add custom validation to prevent giving permissions to 'NT Authority\All Authenticated Users'. The following summarizes the steps needed to be performed to achieve this

1. Create a custom HTTPModule and bind it to the desired sharepoint web application.
2. In the custom http module check if the page is "aclinv.aspx". Catch the init event of the page.
3. In the init event of the page add a custom validator.
4. Perform the validation in the ServerValidate event of the custom validator.

Let me demonstrate with more details

Create a new class and inherit it from the IHTTPModule class. Override the init and dispose method. Catch the PreRequestHandlerExecute event of the HttpApplication class.  This event is triggered just before the page execution starts.

        public void Init(HttpApplication context)
{
context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);
}

In the PreRequestHandlerExecute event attach to the Init event of the Page class as shown below

 void context_PreRequestHandlerExecute(object sender, EventArgs e)
{
HttpApplication httpApp = sender as HttpApplication;
if (httpApp != null)
{
page = httpApp.Context.CurrentHandler as Page;
if (page != null)
{
if (HttpContext.Current.Request.Url.AbsoluteUri.Contains("_layouts/aclinv.aspx"))
{
page.Init += new EventHandler(page_Init);
}
}
}
}

 

Looks simple enough. Now we just need to add a custom validator control to the page. I want the custom validator control to be displayed just below the "Add Users" field. To figure out the location i opened the "aclinv.aspx" page in the Layouts folder. You can see that the Add Users field is a PeopleEditor control

<asp:Content ContentPlaceHolderId="PlaceHolderMain" runat="server">
<TABLE border="0" cellspacing="0" cellpadding="0" class="ms-propertysheet">
<wssuc:InputFormSection Title="<%$Resources:wss,aclinv_Step1Title%>"
Description="<%$Resources:wss,aclinv_DescGrantIndiv%>"
runat="server">
<Template_Description>
<SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,aclinv_DescGrantIndiv%>" EncodeMethod='HtmlEncode'/>
<br>
<br>
<asp:LinkButton id="LinkAddAuthUsers" Text="<%$Resources:wss,permsetup_addauthtitle%>" runat="server"
CausesValidation="false" OnClick="LinkAddAuthUsers_Click" />
</Template_Description>
<Template_InputFormControls>
<wssuc:InputFormControl LabelText="<%$Resources:wss,aclinv_UserGroupsLabel%>" runat="server">
<Template_Control>
<wssawc:PeopleEditor
AllowEmpty=false
ValidatorEnabled="true"
id="userPicker"
runat="server"
ShowCreateButtonInActiveDirectoryAccountCreationMode=true
SelectionSet="User,SecGroup,SPGroup"
/>
</Template_Control>
</wssuc:InputFormControl>
</Template_InputFormControls>
</wssuc:InputFormSection>

This is nested in a deep hierarchy of controls and we need to play around to figure out the exact location where we can add the controls. I temporarily added the following bit of code to the httpModule to display the entire control hierarchy of the page.

private string GetControlCollection(Control ctrl, string indent)
{
string str = string.Empty;
foreach (Control child in ctrl.Controls)
{
str += "<br>" + indent + child.GetType().Name + " -- " + child.ID;
if (child.Controls.Count > 0)
{
str += GetControlCollection(child, indent + "&nbsp;&nbsp;&nbsp;");
}
}
return str;
}

I called this function in the page_Init event in the custom HttpModule.

string hierarchy = GetControlCollection(page);
Literal literal = new Literal(){ Text= hierarchy};
page.Controls.Add(literal);

This displayed the entire control hierarchy of the page. Looking at it we can figure out the location of the "Add Users " control which can be accessed using the following code -

Control ctrl = page.Form.FindControl("PlaceHolderMain").Controls[1].FindControl("PlaceHolderControls").Controls[1].FindControl("PlaceHolderControl").FindControl("userPicker");

Thats it we are almost done. Just need to add the custom validator and handle the ServerValidate event of it.

The code will look something like this.

In the page_Init

                Control ctrl = page.Form.FindControl("PlaceHolderMain").Controls[1].FindControl("PlaceHolderControls").Controls[1]. FindControl("PlaceHolderControl").FindControl("userPicker"); //get reference to the "Add User"s control
CustomValidator cust1 = new CustomValidator();
cust1.ServerValidate += new ServerValidateEventHandler(cust1_ServerValidate); // Handle the event to perform validation
cust1.ControlToValidate = "userPicker";
cust1.Text = "'NT Authority\\All Authenticated Users' is disabled and cannot be added to any group. Please contact your system administrator"; //set the error message to be displayed if the validation fails
ctrl.Parent.Controls.AddAt(ctrl.Parent.Controls.IndexOf(ctrl) + 1, cust1); //add the custom validator just after the Add Users control

In the cust1_ServerValidate

      
            string[] arr = args.Value.Split(',');
foreach (string str in arr)
{
if (string.Compare(str, "nt authority\\authenticated users", StringComparison.InvariantCultureIgnoreCase) == 0) //user entered "NT Authority\Authenticated Users" as one of the options
{
args.IsValid = false; //fail the validation
return;
}
}

The following is the complete code for the HttpModule

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SharePoint.WebControls;
namespace poc_httpmodule
{
public class ValidatorModule : IHttpModule
{
Page page;
public void Init(HttpApplication context)
{
context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);
}
void context_PreRequestHandlerExecute(object sender, EventArgs e)
{
HttpApplication httpApp = sender as HttpApplication;
if (httpApp != null)
{
page = httpApp.Context.CurrentHandler as Page;
if (page != null)
{
if (HttpContext.Current.Request.Url.AbsoluteUri.Contains("_layouts/aclinv.aspx"))
{
page.Init += new EventHandler(page_Init);
}
}
}
}
void page_Init(object sender, EventArgs e)
{
if (page != null)
{
Control ctrl = page.Form.FindControl("PlaceHolderMain").Controls[1].FindControl("PlaceHolderControls"). Controls[1]. FindControl("PlaceHolderControl").FindControl("userPicker"); //get reference to the "Add User"s control
CustomValidator cust1 = new CustomValidator();
cust1.ServerValidate += new ServerValidateEventHandler(cust1_ServerValidate); // Handle the event to perform validation
cust1.ControlToValidate = "userPicker";
cust1.Text = "'NT Authority\\All Authenticated Users' is disabled and cannot be added to any group. Please contact your system administrator"; //set the error message to be displayed if the validation fails
ctrl.Parent.Controls.AddAt(ctrl.Parent.Controls.IndexOf(ctrl) + 1, cust1); //add the custom validator just after the Add Users control
}
}
void cust1_ServerValidate(object source, ServerValidateEventArgs args)
{
string[] arr = args.Value.Split(',');
foreach (string str in arr)
{
if (string.Compare(str, "nt authority\\authenticated users", StringComparison.InvariantCultureIgnoreCase) == 0) //user entered "NT Authority\Authenticated Users" as one of the options
{
args.IsValid = false; //fail the validation
return;
}
}
}
public void Dispose()
{
}

}
}

Last step is to plug in our HttpModule. Add a strong name to the project and add the dll to the gac. Add the following in the <httpModules> tag.  Replace the "Public Key Token" to the appropriate value

<add name="ValidatorModule" type="poc_httpmodule.ValidatorModule, poc_httpmodule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7f1d0bb0d60d5701" />

Now browse to the intended sharepoint site and try to add "NT Authority\Authenticated Users".  You should see the following message

Seems amazing doesn't it. One really cannot help thinking of the amazing things that can be achieved using the power of asp.net. If you thought sharepoint puts too many restrictions compared to asp.net think again :-).

 

Happy Programming
Sohail