Archive

Archive for July, 2013

SharePoint: How to add Links and Headings to Quick Launch programmatically

July 25th, 2013 No comments

    The Quick Launch is a collection of links usually available on every page in the left sidebar (of course, in case you use a standard master page and don’t open the page within a modal dialog), see the image below:

Add Heading to Quick Launch

The links are pretty easily manageable through the UI using Site Settings -> Quick Launch. There Headings and links can be added, removed or reordered. Besides UI, manipulations of the Quick Launch can be done through the SharePoint Object Model. For example, to add a link to the Quick Launch I usually use the following method:

public static SPNavigationNode AddLinkToQuickLaunch(SPNavigationNode parentNode, 
                                        string title, string url, bool isExternal)
{
	SPNavigationNode node = new SPNavigationNode(title, url, isExternal);
	parentNode.Children.AddAsLast(node);

	// refresh navigation node just in case
	node = parentNode.Navigation.GetNodeById(node.Id);
	return node;
}

The parentNode here represents either an existent link or the root SPWeb.Navigation.QuickLaunch object.

Unlike UI where the New Heading option is presented (see the image above), SharePoint Object Model doesn’t provide any special method or node class that would be intended to add a Heading to the Quick Launch. However, we still can turn a usual SPNavigationNode into Heading. For that we have to set some properties available through the SPNavigationNode.Properties. The listing below demonstrates two methods allowing to add a Heading:

public static SPNavigationNode AddHeadingToQuickLaunch(SPWeb spWeb, string headingName)
{
	SPNavigationNodeCollection quicklaunchNav = spWeb.Navigation.QuickLaunch;

	SPNavigationNode headingNode = 
                      new SPNavigationNode(headingName, "javascript:window.goback(0)", true);
	headingNode = quicklaunchNav.AddAsLast(headingNode);

	//turn the node into Heading
	TurnIntoHeading(headingNode);

	headingNode.Update();

	// refresh navigation node just in case
	headingNode = spWeb.Navigation.GetNodeById(headingNode.Id);
	return headingNode;
}
public static void TurnIntoHeading(SPNavigationNode node)
{
	node.Properties["NodeType"]             = "Heading";
	node.Properties["BlankUrl"]             = "True";
	
	node.Properties["LastModifiedDate"]     = DateTime.Now;
	node.Properties["Target"]               = "";
	node.Properties["vti_navsequencechild"] = "true";
	node.Properties["UrlQueryString"]       = "";
	node.Properties["CreatedDate"]          = DateTime.Now;
	node.Properties["Description"]          = "";
	node.Properties["UrlFragment"]          = "";
	node.Properties["Audience"]             = "";
}

Note that the given implementation makes the Heading void, i.e. clicking the Heading neither leads to another page nor refreshes the current one. That’s possible due to the javascript:window.goback(0) that is passed as a URL of the node. I consider Heading as a name of the group of subjacent links, therefore I prefer having void links for Headings.

SharePoint: SqlMembershipProvider – Lock User

July 21st, 2013 No comments

    In addition to the article SharePoint: SqlMembershipProvider – Get All Users In Role, here is one more method to extend the SqlMembershipProvider with. It’s found out that the SqlMembershipProvider doesn’t provide a method to lock user. By default a user can be automatically locked after several frequent and failed attempts to login. To unlock such users the SqlMembershipProvider supplies with the UnlockUser method. But what if administrator wants to temporarily lock user for some reason? Unfortunately, there is no such method out-of-box.

So, let’s try to implement our own LockUser method. Two obvious steps for that are as follows: to create a Stored Procedure in database; to extend a class derived from the SqlMembershipProvider with the proper method.

LockUser Stored Procedure

The stored procedure is very simple as we need just to update one field in the aspnet_Membership table for appropriate user. Below is the script to create such procedure. Run the script on MembershipProvider database, in my case it’s aspnetdb.

USE [aspnetdb]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

-- =============================================
-- Author:      .Net Follower
-- Description:	Locks User
-- =============================================
CREATE PROCEDURE [dbo].[aspnet_Membership_LockUser]
	@ApplicationName                         nvarchar(256),
    @UserName                                nvarchar(256)
AS
BEGIN
	DECLARE @UserId uniqueidentifier
    SELECT  @UserId = NULL
    SELECT  @UserId = u.UserId
    FROM    dbo.aspnet_Users u, dbo.aspnet_Applications a, dbo.aspnet_Membership m
    WHERE   LoweredUserName = LOWER(@UserName) AND
            u.ApplicationId = a.ApplicationId  AND
            LOWER(@ApplicationName) = a.LoweredApplicationName AND
            u.UserId = m.UserId

    IF ( @UserId IS NULL )
        RETURN 1

    UPDATE dbo.aspnet_Membership 
    SET IsLockedOut = 1 WHERE @UserId = UserId

    RETURN 0
END

Custom Membership Provider

Now we can add the LockUser method to the custom Membership Provider called SqlMembershipProviderEx and shown in the article. The SqlMembershipProviderEx with the LockUser is listed below. Note that the methods mentioned in the previous article are skipped.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using System.Reflection;
using System.Web.Security;
using Microsoft.SharePoint;

namespace dotNetFollower
{
    public class SqlMembershipProviderEx : SqlMembershipProvider
    {       
	    ...
		
        public bool LockUser(string username)
        {
            bool flag = false;
            CheckParameter(ref username, true, true, true, 0x100, "username");

            DoInSqlConnectionContext(delegate(SqlConnection connection)
            {
                //this.CheckSchemaVersion(connection.Connection);
                SqlCommand command = new SqlCommand("dbo.aspnet_Membership_LockUser", connection)
                {
                    CommandTimeout = CommandTimeout,
                    CommandType    = CommandType.StoredProcedure
                };
                command.Parameters.Add(CreateInputParam("@ApplicationName", SqlDbType.NVarChar, ApplicationName));
                command.Parameters.Add(CreateInputParam("@UserName", SqlDbType.NVarChar, username));
                SqlParameter parameter = new SqlParameter("@ReturnValue", SqlDbType.Int)
                {
                    Direction = ParameterDirection.ReturnValue
                };
                command.Parameters.Add(parameter);

                command.ExecuteNonQuery();
                flag = ((parameter.Value != null) ? ((int)parameter.Value) : -1) == 0;
            });

            return flag;
        }

        protected internal static void CheckParameter(ref string param, bool checkForNull, bool checkIfEmpty, bool checkForCommas, int maxSize, string paramName)
        {
            if (param == null)
            {
                if (checkForNull)
                    throw new ArgumentNullException(paramName);
            }
            else
            {
                param = param.Trim();
                if (checkIfEmpty && (param.Length < 1))
                    throw new ArgumentException(string.Format("The parameter '{0}' must not be empty.", new object[] { paramName }), paramName);

                if ((maxSize > 0) && (param.Length > maxSize))
                    throw new ArgumentException(string.Format("The parameter '{0}' is too long: it must not exceed {1} chars in length.", new object[] { paramName, maxSize.ToString(CultureInfo.InvariantCulture) }), paramName);

                if (checkForCommas && param.Contains(","))
                    throw new ArgumentException(string.Format("The parameter '{0}' must not contain commas.", new object[] { paramName }), paramName);
            }
        }

        ...
        
    }    
}

The latest version of the SqlMembershipProviderEx along with all used additional classes are available to download here.

Related posts:

SharePoint: HttpContext.Current is null in event receivers

July 12th, 2013 No comments

    I have never used HttpContext in event receivers till recently, so I was quite surprised when I got a NullReferenceException, trying to access HttpContext.Current.Request within ItemAdding. I would never play with the HttpContext.Current inside such methods as ItemAdded, ItemUpdated and so on as they are usually asynchronous and might be executed on any machine of SharePoint farm. But why the HttpContext.Current is null within synchronous ItemAdding, ItemUpdating, etc. it’s a riddle for me. On the other hand, within the constructor of SPItemEventReceiver the HttpContext.Current is valid. So, the possible workaround here is to get current HttpContext inside the constructor, save it in a variable and then use in synchronous methods. In my opinion the best way in this case is to have a class that is derived from SPItemEventReceiver, manipulates HttpContext and serves as a base class for all custom event receivers. Such simple class could resemble the following:

public class MyAppSPItemEventReceiverBase : SPItemEventReceiver
{
	protected readonly HttpContext _currentContext = null;

	public MyAppSPItemEventReceiverBase()
	{
		_currentContext = HttpContext.Current;
	}
}

Every custom event receiver in that case should look like the following:

public class SomeCustomEventReceiver : MyAppSPItemEventReceiverBase
{
	public override void ItemUpdating(SPItemEventProperties properties)
	{
		base.ItemUpdating(properties);

		properties.AfterProperties["UpdatedFrom"] = GetIpAddress(_currentContext);
	}

	protected static string GetIpAddress(HttpContext context)
	{
		string ipAddress = context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
		if (string.IsNullOrEmpty(ipAddress))
			return context.Request.ServerVariables["REMOTE_ADDR"];
		string[] tmpArray = ipAddress.Split(',');
		return tmpArray[0];
	}
}

SharePoint: What is a People Picker? Part 2 – Picker.aspx and PeoplePickerDialog

July 2nd, 2013 No comments

In the previous article about People Picker functionality I described the PeopleEditor control. The Browse button of the PeopleEditor initiates the search dialog containing the Picker.aspx page described below.

Picker.aspx

Picker.aspx is a page used for many built-in entity pickers and, along with its master-page pickerdialog.master, located at 14\TEMPLATE\LAYOUTS. Both the page and master-page contain a lot of PlaceHolders that are stuffed with different controls and contents depending on the type of the picker we use. In case of the People Picker the most noteworthy PlaceHolders defined in the Picker.aspx are

  • PlaceHolderDialogControl;
  • PlaceHolderQueryControl;
  • PlaceHolderHtmlMessage;
  • PlaceHolderError;
  • PlaceHolderResultControl;
  • PlaceHolderEditorControl.

Take a look at a simplified markup below to figure out where these PlaceHolders are situated within the Picker.aspx (the Html comments, indents and formatting are added for clarity):

Click to open the Picker.aspx’s markup with PlaceHolders

...
<asp:Content contentplaceholderid="PlaceHolderDialogBodySection" 
    runat="server">
 ...
 <!-- PlaceHolderDialogControl -->
 <asp:PlaceHolder runat="server" id="PlaceHolderDialogControl"/>
	
  <table class="ms-pickerbodysection" cellspacing="0" cellpadding="0" 
          width='100%' height='100%'>
   ...
   <tr>
    <td width='15px'>&#160;</td>
    <td width='100%' height="20px">
     <!-- PlaceHolderQueryControl -->
     <asp:PlaceHolder runat="server" id="PlaceHolderQueryControl"/>
    </td>
    <td width='15px'>&#160;</td>
   </tr>
   <tr>
    <td width='15px'>
     <img src="/_layouts/images/blank.gif" width='15' height='1' alt="" />
    </td>
    <td class="ms-descriptiontext">
     <!-- PlaceHolderHtmlMessage -->
     <asp:PlaceHolder runat="server" id="PlaceHolderHtmlMessage"/>
    </td>
    <td width='15px'>
     <img src="/_layouts/images/blank.gif" width='15' height='1' alt="" />
    </td>
   </tr>
   <tr>
    <td width='15px'>&#160;</td>
    <td class="ms-descriptiontext" style="color:red;">
     <!-- PlaceHolderError -->
     <asp:PlaceHolder runat="server" id="PlaceHolderError"/>
    </td>
    <td width='15px'>&#160;</td>
   </tr>
   <tr height='100%'>
    <td width='15px'>
     <img src="/_layouts/images/blank.gif" width='15' height='200' alt="" />
    </td>
    <td>
     <table cellspacing="0" cellpadding="0" width='100%' height='100%' 
              class="ms-pickerresultoutertable">
      <tr height="100%">
       ...
       <td id="ResultArea">
        <div id='resultcontent' class="ms-pickerresultdiv">
         <!-- PlaceHolderResultControl -->
         <asp:PlaceHolder runat="server" id="PlaceHolderResultControl"/>
        </div>
       </td>
      </tr>
     </table>
    </td>
    <td width='15px'>
     <img src="/_layouts/images/blank.gif" width='15' height='200' alt="" />
    </td>
   </tr>
   ...
   <tr id="EditorRow" runat="server">
    <td width='15px'>&#160;</td>
    <td width="100%">
     <table width="100%" cellspacing="0" cellpadding="0">
      <tr>
       <td>
        <input type='button' id='AddSel' runat='server' disabled
          class='ms-NarrowButtonHeightWidth' onclick='addSelected_Click();' 
          accesskey="<%$Resources:wss,picker_AddSelAccessKey%>" />
       </td>
       <td width="10px">
        <img src="/_layouts/images/blank.gif" width='4' height='1' alt="" />
       </td>
       <td width='100%'>
        <!-- PlaceHolderEditorControl -->
        <asp:PlaceHolder runat="server" id="PlaceHolderEditorControl"/>
       </td>
      </tr>
     </table>
    </td>
    <td width='15px'>&#160;</td>
   </tr>
   ...
  </table>
...
</asp:Content>

Clicking the Browse button of PeopleEditor requests the Picker.aspx with a number of query string parameters. Below is an example of such url after url decoding (indents and formatting are added for clarity):

http://servername/mysitecollection/_layouts/Picker.aspx?
      MultiSelect=True&
      CustomProperty=User,SecGroup,DL;;15;;;False&
      DialogTitle=Select People and Groups&
      DialogImage=/_layouts/images/ppeople.gif&
      PickerDialogType=Microsoft.SharePoint.WebControls.PeoplePickerDialog, 
                       Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, 
                       PublicKeyToken=71e9bce111e9429c&
      ForceClaims=False&
      DisableClaims=False&
      EnabledClaimProviders=&
      EntitySeparator=;;??????&
      DefaultSearch=

Among search and dialog options there is such parameter as PickerDialogType containing a fully qualified type name, in this case it’s PeoplePickerDialog. Having read this parameter the Picker.aspx creates an instance of PeoplePickerDialog through the Reflection. Below is the shortened code doing that, this code is taken from the OnLoad method of the Picker.aspx:

...
// get the type
Type type = Utility.GetTypeFromAssembly(Request["PickerDialogType"], true, false);
...        
// create instance through Reflection
this.DialogControl = (PickerDialog) Activator.CreateInstance(type);
	
...
// set some search options based on query string parameters
this.DialogControl.CustomProperty = Request["CustomProperty"];
...
string str3 = Request["MultiSelect"];
if (str3 != null)
  this.DialogControl.MultiSelect = bool.Parse(str3);

PeoplePickerDialog

PeoplePickerDialog, a control the main goal of which is to supply with other visual controls to be put into PlaceHolders of the Picker.aspx. So, the PeoplePickerDialog creates some controls and exposes references to them as properties, but doesn’t add them to its inner Controls-collection and, therefore, doesn’t render them on its own. Let’s see what controls are created by PeoplePickerDialog. Below are constructors of the PeoplePickerDialog and its base class, PickerDialog, respectively:

// the constructor of PeoplePickerDialog
public PeoplePickerDialog() : base(new PeopleQueryControl(), 
                                   new HierarchyResultControl(), 
                                   new PeopleEditor(), true)
{
    ...
}
	
// the main constructor of the base class, PickerDialog
public PickerDialog(PickerQueryControlBase query, PickerResultControlBase result, 
                    EntityEditorWithPicker editor)
{
   ...		
   this.ErrorMessageLabel = new Label();
   // the ErrorMessageLabel field is available through the ErrorLabel property
		
   this.m_HtmlMessageLabel = new Label();
   // the m_HtmlMessageLabel field is available through the HtmlMessageLabel property

   this.QueryControlValue = query;
   // the QueryControlValue field is available through the QueryControl property
		
   this.ResultControlValue = result;
   // the ResultControlValue field is available through the ResultControl property
		
   this.EditorControlValue = editor;
   // the EditorControlValue field is available through the EditorControl property
   ...
}

As we can see, when the Picker.aspx instantiates the object of PeoplePickerDialog through the Reflection a number of visual controls are created. How does the Picker.aspx use them? Below is the code taken again from the OnLoad method of the Picker.aspx:

...
// add the PeoplePickerDialog itself to the PlaceHolderDialogControl of the picker.aspx.   
this.PlaceHolderDialogControl.Controls.Add(this.DialogControl);
   
// add controls supplied by the PeoplePickerDialog to some PlaceHolders of the picker.aspx
this.PlaceHolderEditorControl.Controls.Add(this.DialogControl.EditorControl);
this.PlaceHolderResultControl.Controls.Add(this.DialogControl.ResultControl);
this.PlaceHolderQueryControl.Controls.Add(this.DialogControl.QueryControl);
...
// add controls supplied by the PeoplePickerDialog to some PlaceHolders of the picker.aspx   
this.PlaceHolderError.Controls.Add(this.DialogControl.ErrorLabel);
this.PlaceHolderHtmlMessage.Controls.Add(this.DialogControl.HtmlMessageLabel);

The picture below demonstrates where the basic controls are located within the dialog:

Basic Controls of the Search Dialog

Note the HtmlMessageLabel and ErrorLabel are not presented in the picture, but they reside in the appropriate PlaceHolders between PeopleQueryControl and HierarchyResultControl.

As we remember the PeopleEditor opens the search dialog. Interestingly that the dialog uses the same PeopleEditor control to hold selected users and groups. Note, however, the PeopleEditor within the dialog hides its buttons (Check Names and Browse) and doesn’t allow typing anything.

Besides creating and exposing the controls the PeoplePickerDialog also provides with some JavaScripts, exposes search parameters, defines some elements of the dialog’s appearance (title, for example), defines columns to be displayed in the table of search results and so on.

Related posts:

SharePoint: How to change the expiration time of the FedAuth cookie

July 2nd, 2013 No comments

    Working on a SharePoint application with the configured Form Based Authentication (FBA), I was asked to reduce somehow the expiration time of the FedAuth cookie. The default expiration time is 10 hours, that is too long for applications with sensitive data. I’d like to limit it with 20 minutes.

As known, the Security Token Service takes part in SharePoint Authentication by issuing, managing and validating security tokens. When the SharePoint Authentication process is initiated, the login and password are passed to the Security Token Service. The Security Token Service, in turn, generates a security token and passes it back to SharePoint. SharePoint then creates a FedAuth cookie based on the issued security token and adds it to the Response. Once the cookie is sent to the client it’s stored there in the local cookies folder. Every next request for the site is accompanied with the cookie, unless it’s expired. SharePoint reads the cookie from requests and provides access to the content without re-authentication.

The default expiration time is a setting of the Security Token Service. We can change it using such PowerShell command as

Set-SPSecurityTokenServiceConfig –FormsTokenLifetime [value in minutes]

That’s well described here. Note, however, if you change the setting it affects the whole SharePoint Farm, so FedAuth cookies issued for other applications will have the same expiration time. From that point of view, the solution isn’t acceptable for me.

Fortunately, I found an alternative way to change the expiration time so that it would impact particular application only. The solution turned out quite easy and straightforward. Within codebehind of the Custom Login page and after user is authenticated, we can just get access to the cookie placed in the Response object and forcibly set another expiration time. So, in my case I have the following code in the Custom Login page:

public partial class CustomLoginPage : FormsSignInPage
{
	...

	protected override void OnInit(EventArgs e)
	{
		base.OnInit(e);
		
		// subscribe to Authenticate event of the asp:Login control
		signInControl.Authenticate += SignInControlOnAuthenticate;
	}

	private void SignInControlOnAuthenticate(object sender, AuthenticateEventArgs authenticateEventArgs)
	{
		// authenticate user
		bool isAuthenticated = SPClaimsUtility.AuthenticateFormsUser(Context.Request.Url, signInControl.UserName, signInControl.Password);
		if (isAuthenticated)
		{
			authenticateEventArgs.Authenticated = true;

			// forcibly change the expiration time of the FedAuth cookie
			HttpCookie cookie = Response.Cookies[0];
			cookie.Expires    = DateTime.UtcNow.AddMinutes(20);
			
			// redirect user to somewhere
			SPUtility.Redirect("some other url", SPRedirectFlags.Default, Context);
		}
	}
}

In the code above I set the cookie’s life time to 20 minutes. You can use the code to increase or decrease the default expiration time.

If you don’t use a Custom Login page, I believe (but didn’t test) it’s possible to achieve the same by employing a HttpModule with handler of the EndRequest event being fired by the HttpApplication object.