Archive

Posts Tagged ‘SharePoint 2010’

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.

SharePoint: SqlMembershipProvider – Get All Users In Role

June 30th, 2013 No comments

    In the SharePoint application I’m currently working on, I configured Form Based Authentication (FBA) using the SqlMembershipProvider and SqlRoleProvider. Implementing some user management functionality, I run into the lack of a method to get the users in particular role by portions (so-called pagination). The SqlRoleProvider exposes the GetUsersInRole method which returns only names of users in the passed role and doesn’t support pagination. The direct way in this case is to get user names and then get appropriate users, calling the GetUser method of SqlMembershipProvider one time per name. This approach results in a bunch of requests to the database: one request is to get names of users in a role and a number of requests are to get each user by his name. In addition, we have somehow to implement pagination ourselves. The approach is acceptable, but let’s try to reduce requests to the database and borrow somewhere the pagination logic.

GetAllUsersInRole Stored Procedure

It’s interesting that the SqlMembershipProvider provides the GetAllUsers method that supports pagination. On the database level, every call of SqlMembershipProvider.GetUsersInRole and SqlMembershipProvider.GetAllUsers ends with executing such Stored Procedures as aspnet_UsersInRoles_GetUsersInRoles and aspnet_Membership_GetAllUsers respectively. So, we know that the aspnet_UsersInRoles_GetUsersInRoles searches for names of users in a role while the aspnet_Membership_GetAllUsers is able to return users by portions. Let’s combine these two Stored Procedures and create another one which would select users in a role and return a required portion of the result. The sql script below creates such Stored Procedure, I named it aspnet_Membership_GetAllUsersInRole. Note the script should be executed on MembershipProvider database, it’s aspnetdb in my case.

USE [aspnetdb]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

-- =============================================
-- Author:		.Net Follower
-- Description:	Returns users in role by portions
-- =============================================
CREATE PROCEDURE [dbo].[aspnet_Membership_GetAllUsersInRole]
    @ApplicationName       nvarchar(256),
    @PageIndex             int,
    @PageSize              int,
    @RoleName              nvarchar(256)
AS
BEGIN
    DECLARE @ApplicationId uniqueidentifier
    SELECT  @ApplicationId = NULL
    SELECT  @ApplicationId = ApplicationId FROM dbo.aspnet_Applications WHERE LOWER(@ApplicationName) = LoweredApplicationName
    IF (@ApplicationId IS NULL)
        RETURN 0

	DECLARE @RoleId uniqueidentifier
    SELECT  @RoleId = NULL

    SELECT  @RoleId = RoleId
    FROM    dbo.aspnet_Roles
    WHERE   LOWER(@RoleName) = LoweredRoleName AND ApplicationId = @ApplicationId

    IF (@RoleId IS NULL)
		RETURN 0

    -- Set the page bounds
    DECLARE @PageLowerBound int
    DECLARE @PageUpperBound int
    DECLARE @TotalRecords   int
    SET @PageLowerBound = @PageSize * @PageIndex
    SET @PageUpperBound = @PageSize - 1 + @PageLowerBound

    -- Create a temp table TO store the select results
    CREATE TABLE #PageIndexForUsers
    (
        IndexId int IDENTITY (0, 1) NOT NULL,
        UserId uniqueidentifier
    )

    -- Insert into our temp table
    INSERT INTO #PageIndexForUsers (UserId)
    SELECT u.UserId
    FROM   dbo.aspnet_Membership m, dbo.aspnet_Users u, dbo.aspnet_UsersInRoles ur
    WHERE  u.ApplicationId = @ApplicationId AND u.UserId = m.UserId AND 
		   u.UserId = ur.UserId AND @RoleId = ur.RoleId
    ORDER BY u.UserName

    SELECT @TotalRecords = @@ROWCOUNT

    SELECT u.UserName, m.Email, m.PasswordQuestion, m.Comment, m.IsApproved,
            m.CreateDate,
            m.LastLoginDate,
            u.LastActivityDate,
            m.LastPasswordChangedDate,
            u.UserId, m.IsLockedOut,
            m.LastLockoutDate
    FROM   dbo.aspnet_Membership m, dbo.aspnet_Users u, #PageIndexForUsers p
    WHERE  u.UserId = p.UserId AND u.UserId = m.UserId AND
           p.IndexId >= @PageLowerBound AND p.IndexId <= @PageUpperBound
    ORDER BY u.UserName
    RETURN @TotalRecords
END

Custom Membership Provider

Now let’s extend our Membership Provider with a new method that deals with the aspnet_Membership_GetAllUsersInRole. I created a class SqlMembershipProviderEx derived from SqlMembershipProvider and containing the target GetAllUsersInRole method. The class is demonstrated below, but first of all a few remarks on the code:

  • I had to use Reflection to get values of some important fields (like Connection String to the database, for example) as Microsoft makes everything private or internal;
  • The SqlMembershipProvider elevates privileges when opening SqlConnection. Since the extended Membership Provider is going to be used in SharePoint application, I did the same by means of SPSecurity.RunWithElevatedPrivileges. Note however that if you want to use the extended Membership Provider in a pure ASP.Net application you will need to deal with such internal (of course) classes as SqlConnectionHolder and ApplicationImpersonationContext through Reflection;
  • The code of GetAllUsersInRole method is mainly based on the GetAllUsers of the parent SqlMembershipProvider class.
using System;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
using System.Web.Security;
using Microsoft.SharePoint;

namespace dotNetFollower
{
    public class SqlMembershipProviderEx : SqlMembershipProvider
    {
        protected string _connectionString;
        protected int?   _sqlCommandTimeout;

        protected string ConnectionString
        {
            get 
            { 
                return _connectionString ?? 
                    (_connectionString = Convert.ToString(this.GetFieldValue("_sqlConnectionString"))); 
            }
        }

        protected int CommandTimeout
        {
            get
            {
                if (_sqlCommandTimeout == null)
                    _sqlCommandTimeout = Convert.ToInt32(this.GetFieldValue("_CommandTimeout"));
                return _sqlCommandTimeout.Value;
            }
        }

        public MembershipUserCollection GetAllUsersInRole(string role, int pageIndex, int pageSize, out int totalRecords)
        {
            if (pageIndex < 0)
                throw new ArgumentException("The pageIndex must be greater than or equal to zero.", "pageIndex");
            if (pageSize < 1)
                throw new ArgumentException("The pageSize must be greater than zero.", "pageSize");
            
            long num = ((pageIndex * pageSize) + pageSize) - 1;
            if (num > 0x7fffffff)
                throw new ArgumentException("The combination of pageIndex and pageSize cannot exceed the maximum value of System.Int32.", "pageIndex and pageSize");
            
            MembershipUserCollection users = new MembershipUserCollection();
            int recordsAmount = 0;

            DoInSqlConnectionContext(delegate(SqlConnection connection)
                {
                    //this.CheckSchemaVersion(connection.Connection);
                    SqlCommand command     = new SqlCommand("dbo.aspnet_Membership_GetAllUsersInRole", connection);
                    SqlDataReader reader   = null;
                    SqlParameter parameter = new SqlParameter("@ReturnValue", SqlDbType.Int);
                    command.CommandTimeout = CommandTimeout;
                    command.CommandType    = CommandType.StoredProcedure;
                    command.Parameters.Add(CreateInputParam("@ApplicationName", SqlDbType.NVarChar, ApplicationName));
                    command.Parameters.Add(CreateInputParam("@PageIndex", SqlDbType.Int, pageIndex));
                    command.Parameters.Add(CreateInputParam("@PageSize", SqlDbType.Int, pageSize));
                    command.Parameters.Add(CreateInputParam("@RoleName", SqlDbType.NVarChar, role));
                    parameter.Direction = ParameterDirection.ReturnValue;
                    command.Parameters.Add(parameter);
                    try
                    {
                        reader = command.ExecuteReader(CommandBehavior.SequentialAccess);
                        while (reader.Read())
                        {
                            string   nullableString          = GetNullableString(reader, 0);
                            string   email                   = GetNullableString(reader, 1);
                            string   passwordQuestion        = GetNullableString(reader, 2);
                            string   comment                 = GetNullableString(reader, 3);
                            bool     boolean                 = reader.GetBoolean(4);
                            DateTime creationDate            = reader.GetDateTime(5).ToLocalTime();
                            DateTime lastLoginDate           = reader.GetDateTime(6).ToLocalTime();
                            DateTime lastActivityDate        = reader.GetDateTime(7).ToLocalTime();
                            DateTime lastPasswordChangedDate = reader.GetDateTime(8).ToLocalTime();
                            Guid     providerUserKey         = reader.GetGuid(9);
                            bool     isLockedOut             = reader.GetBoolean(10);
                            DateTime lastLockoutDate         = reader.GetDateTime(11).ToLocalTime();
                            users.Add(new MembershipUser(Name, nullableString, providerUserKey, email, passwordQuestion,
                                                         comment, boolean, isLockedOut, creationDate, lastLoginDate,
                                                         lastActivityDate, lastPasswordChangedDate, lastLockoutDate));
                        }
                    }
                    catch (Exception ex)
                    {
                        EventLogger.WriteError(ex);
                        throw;
                    }
                    finally
                    {
                        if (reader != null)
                            reader.Close();
                        if (parameter.Value is int)
                            recordsAmount = (int)parameter.Value;
                    }
                });
            totalRecords = recordsAmount;
            return users;
        }

        protected void DoInSqlConnectionContext(Action<SqlConnection> action)
        {
            SqlConnection connection = null;
            try
            {
                connection = new SqlConnection(ConnectionString);
                SPSecurity.RunWithElevatedPrivileges(connection.Open);
                action(connection);
            }
            finally
            {
                if (connection != null)
                    connection.Close();
            }
        }

        protected SqlParameter CreateInputParam(string paramName, SqlDbType dbType, object objValue)
        {
            SqlParameter parameter = new SqlParameter(paramName, dbType);
            if (objValue == null)
            {
                parameter.IsNullable = true;
                parameter.Value      = DBNull.Value;
                return parameter;
            }
            parameter.Value = objValue;
            return parameter;
        }

        protected string GetNullableString(SqlDataReader reader, int col)
        {
            return !reader.IsDBNull(col) ? reader.GetString(col) : null;
        }
    }
}

Note the EventLogger class is described in the post SharePoint: Simple Event Logger while the GetFieldValue method is provided by ReflectionHelper described in the C#: How to set or get value of a private or internal field through the Reflection and C#: How to set or get value of a private or internal property through the Reflection.

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

Related posts: