Inconvenient SPSecurityTrimmedControl revisited


About a year ago I wrote about the inconvenience of the SPSecurityTrimmedControl: a great idea within the SharePoint framework that unfortunately doesn’t work the way it should. Now, as we’re about to get a new release of SharePoint, I decided to check if things have changed.

What makes SPSecurityTrimmedControl so cool?

In case you didn’t know SharePoint, both 2007 and 2010, ships with a control that allows you to conditionally display/hide pieces of your site: the Microsoft.SharePoint.WebControls.SPSecurityTrimmedControl. You can display/hide the content using a few different criteria, like authentication (for anonymous/authenticated users only), page mode (page in display or edit mode) or current user’s permissions.

…so why it’s so uncool?

From what I’ve seen the control doesn’t work the way you would expect it. Let’s take for example the criteria to include some code for anonymous users only. This is a common way to include web analytics tracking codes. Using the SPSecurityTrimmedControl you would use the following code snippet in your Master Page/Page Layout:

<SharePoint:SPSecurityTrimmedControl runat="server" AuthenticationRestrictions="AnonymousUsersOnly">
Tracking code goes here...
</SharePoint:SPSecurityTrimmedControl>

Unfortunately the tracking code doesn’t get included: neither for authenticated nor for anonymous users. Similar thing applies to the PageModes property that would allow you to display some content in Edit Mode only (a bit like the EditModePanel from SharePoint server does). So far nothing has changed in SharePoint 2010.

And on top of that…

Even if the SPSecurityTrimmedControl worked as it should, it would still have a major flaw. SPSecurityTrimmedControl is meant to display conditionally content: this can be plain text, HTML or ASP.NET controls. The problem is with how it determines whether it should show its contents or not. As the SPSecurityTrimmedControls performs the check almost at the end of its lifecycle in the Render method, almost every method of every child control gets executed: even if it won’t be included in the generated HTML!

With all that I decided to create a custom SecurityTrimmedControl that would:

  1. Work as you would expect it to.
  2. Prevent the child controls from being executed if the whole control is hidden.

Proudly introducing the ImtechSecurityTrimmedControl:

[ParseChildren(true)]
public class ImtechSecurityTrimmedControl : Control
{
    public ITemplate ContentTemplate { get; set; }

    public AuthenticationRestrictions AuthenticationRestrictions { get; set; }
    public SPControlMode PageMode { get; set; }

    private static AuthenticationRestrictions CurrentAuthenticationRestriction
    {
        get
        {
            AuthenticationRestrictions currentAuthenticationRestriction = HttpContext.Current.Request.IsAuthenticated ? AuthenticationRestrictions.AuthenticatedUsersOnly : AuthenticationRestrictions.AnonymousUsersOnly;

            return currentAuthenticationRestriction;
        }
    }

    private static SPControlMode CurrentPageMode
    {
        get
        {
            SPControlMode pageMode = SPControlMode.Invalid;

            if (HttpContext.Current != null)
            {
                HttpRequest request = HttpContext.Current.Request;
                if (IsDocLibListItem)
                {
                    pageMode = (request.Form.Get("MSOAuthoringConsole_FormContext") == "1") ? SPControlMode.Edit : SPControlMode.Display;
                }

                if ((pageMode == SPControlMode.Display) && (request.QueryString.Get("ControlMode") == "Edit"))
                {
                    pageMode = SPControlMode.Edit;
                }
            }

            return pageMode;
        }
    }

    private static bool IsDocLibListItem
    {
        get
        {
            return SPContext.Current != null &&
                SPContext.Current.ListItem != null &&
                SPContext.Current.ItemId != 0;
        }
    }

    private bool ShouldRender
    {
        get
        {
            return AuthenticationRestrictionMatchesCurrentRequest(AuthenticationRestrictions) && PageIsInMode(PageMode);
        }
    }

    internal bool PageIsInMode(SPControlMode pageMode)
    {
        return pageMode == 0 ||
            CurrentPageMode == pageMode;
    }

    private static bool AuthenticationRestrictionMatchesCurrentRequest(AuthenticationRestrictions authenticationRestrictions)
    {
        return authenticationRestrictions == 0 ||
            authenticationRestrictions == AuthenticationRestrictions.AllUsers ||
            CurrentAuthenticationRestriction == authenticationRestrictions;
    }

    protected override void CreateChildControls()
    {
        base.CreateChildControls();

        if (ContentTemplate != null && ShouldRender)
        {
            Control container = new Control();
            ContentTemplate.InstantiateIn(container);
            Controls.Add(container);
        }
    }
}

The code is pretty straight-forward. After performing a few checks the control uses a template to instantiate its children if required. Because the ImtechSecurityTrimmedControl doesn’t have any HTML markup of its own, it derives from the System.Web.UI.Control base class. Because of that the ParseChildren attribute has to be explicitly set to true to make the control parse the ContentTemplate. If we inherited from the WebControl base class this would be done for us, but then we would end up with a pretty div in the generated HTML.

One thing that I didn’t include is the support for permissions. The reason for this is pretty simple: in all the projects I’ve worked on so far, I have never had a need to use that kind of functionality. And in case you need it: you can easily extend the control to support it.

Technorati Tags: SharePoint,SharePoint 2010,SharePoint 2007

Others found also helpful: