Search by Email in the Sitecore User Manager

I’m sure I am not alone when I criticize the standard Sitecore user manager tool. It is adequate for basic Sitecore solutions but isn’t able to keep up with more complex implementations that may have many different types of Sitecore users and domains.

One issue that is frequently cited by clients is that the search functionality in the Sitecore user manager can only search on the User Name and not other fields like email address. This can become a real problem, especially if you are using email addresses instead of user names for authenticating extranet users.

An older post from 2010 (Teach User Manager how to search by email) is the only reference I have come accross on this topic and it proved to be an excellent starting point. However the previous post does have 2 issues that I was looking to solve.

  1. The code in the post above no longer compiles against newer versions of Sitecore.
  2. The post above only provides a partial solution. It only works for users that have the admin checkbox selected in their user profile. This solution does not allow non-admin users to search by email address in the user manager.

Updating the Code (Admin users only)

This part was fairly straightforward as there have only been minor changes made to the Sitecore.Security.Accounts.UserProvider class.

public class CustomUserProvider : UserProvider
{
    protected override IEnumerable<User> GetUsersByName(int pageIndex, int pageSize, string userNameToMatch)
    {
        return FindUsers(pageIndex, pageSize, userNameToMatch);
    }

    protected IEnumerable<User> FindUsers(int pageIndex, int pageSize, string userNameToMatch)
    {
        Assert.ArgumentNotNull(userNameToMatch, "userNameToMatch");
        IEnumerable<User> users;
        string userWithNoWildcard = StringUtil.RemovePostfix(Settings.Authentication.VirtualMembershipWildcard,
            StringUtil.RemovePrefix(Settings.Authentication.VirtualMembershipWildcard, userNameToMatch));

        if (Regex.IsMatch(userWithNoWildcard,
            @"^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$"))
            users = FindByEmail(pageIndex, pageSize, userWithNoWildcard);
        else
            users = FindByUsername(pageIndex, pageSize, userNameToMatch);
        return users;
    }

    protected IEnumerable<User> FindByUsername(int pageIndex, int pageSize, string userNameToMatch)
    {
        Assert.ArgumentNotNull(userNameToMatch, "userNameToMatch");
        int total;
        return
            new Enumerable<User>(
                () => Membership.FindUsersByName(userNameToMatch, pageIndex, pageSize, out total).GetEnumerator(),
                o => User.FromName(((MembershipUser) o).UserName, false));
    }

    protected IEnumerable<User> FindByEmail(int pageIndex, int pageSize, string userNameToMatch)
    {
        Assert.ArgumentNotNull(userNameToMatch, "userNameToMatch");
        int total;
        return
            new Enumerable<User>(
                () => Membership.FindUsersByEmail(userNameToMatch, pageIndex, pageSize, out total).GetEnumerator(),
                o => User.FromName(((MembershipUser) o).UserName, false));
    }
}

And here is the associated custom config file.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <userManager>
      <patch:attribute name="defaultProvider">custom</patch:attribute>
      <providers>
        <clear/>
        <add name="custom" type="CustomUserProvider, __ASSEMBLY__"/>
      </providers>
    </userManager>
  </sitecore>
</configuration>

Admin users will now be able to search by email address in the Sitecore user manager. Keep in mind that this implementation requires an exact match when searching by email address. If no users are returned for the given email address then the standard results are displayed based on user name.

sitecore_user_manager

The Other Half of the Solution (Non-admin users only)

While the solution above only works for admin users, the solution provided below allows non-admin users to also search by email address in the Sitecore user manager.

public class CustomDomain : Domain
{
    public override IEnumerable<User> GetUsersByName(int pageIndex, int pageSize, string search, out int total)
    {
        List<User> userListByEmail = GetUserListByEmail(search);
        if (userListByEmail.Any())
        {
            total = userListByEmail.Count;
            return userListByEmail;
        }
        MembershipUserCollection usersByName = Membership.FindUsersByName(AccountPrefix + search, pageIndex,
            pageSize, out total);
        var list = new List<User>();
        foreach (MembershipUser membershipUser in usersByName)
            list.Add(User.FromName(membershipUser.UserName, false));
        return list;
    }

    public override int GetUsersByNameCount(string search)
    {
        int totalRecords;
        List<User> userListByEmail = GetUserListByEmail(search);
        if (userListByEmail.Any())
            return userListByEmail.Count;
        Membership.FindUsersByName(GetSearchString(search), 0, 1, out totalRecords);
        return totalRecords;
    }

    public List<User> GetUserListByEmail(string search)
    {
        string searchWithNoWildcard = StringUtil.RemovePostfix(Settings.Authentication.VirtualMembershipWildcard,
            StringUtil.RemovePrefix(Settings.Authentication.VirtualMembershipWildcard, search));
        var list = new List<User>();
        if (Regex.IsMatch(searchWithNoWildcard,
            @"^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$"))
        {
            MembershipUserCollection usersByEmail = Membership.FindUsersByEmail(searchWithNoWildcard);
            foreach (MembershipUser membershipUser in usersByEmail)
            {
                User sitecoreUser = User.FromName(membershipUser.UserName, false);
                if (sitecoreUser.Domain.Name + "\\" == AccountPrefix)
                    list.Add(sitecoreUser);
            }
        }
        return list;
    }
}

The CustomDomain class outlined above should be called from the Domains.config file located in the “Website/App_Config/Security” folder.

The standard Domains.config file contains the following code block.

<sc:templates>
  <domain type="Sitecore.Security.Domains.Domain, Sitecore.Kernel">
    <ensureAnonymousUser>true</ensureAnonymousUser>
    <locallyManaged>false</locallyManaged>
  </domain>
</sc:templates>

And it should be replaced so that the CustomDomain class is added to the type attribute.

<sc:templates>
  <domain type="CustomDomain, __ASSEMBLY__">
    <ensureAnonymousUser>true</ensureAnonymousUser>
    <locallyManaged>false</locallyManaged>
  </domain>
</sc:templates>

Why Two Different Approaches?

Here is a quick explanation if (like me) you’re wondering why it is necessary to customize two different areas in order to achieve the same result for admin and non-admin users.

The Sitecore.Security.Accounts.UserDelegation class implements the GetManagedUsers() method as shown below.

public virtual IEnumerable<User> GetManagedUsers()
{
  if (this._user.Profile.IsAdministrator)
    return (IEnumerable<User>) UserManager.GetUsers();
  else
    return this.DoGetManagedUsers();
}

private IEnumerable<User> DoGetManagedUsers()
{
   return new DomainsWrapper(this.GetManagedDomains()).GetUsers();
}

As you can see, the GetManagedUsers() method calls UserManager.GetUsers() in order to return all users if the “IsAdministrator” flag is set for the context user. If the “IsAdministrator” flag is not set then DoGetManagedUsers() is called which can return a subset of the total users based on domains. This is important since it is the basis of how locally managed domains work in Sitecore.

It is the Sitecore.Shell.Applications.Security.UserManager.UserManager class in the Sitecore.Client assembly that initially calls GetManagedUsers() when the Sitecore user manager is loaded.

In Closing

The two examples displayed above will provide a full solution that allows all types of users to search by email address in the Sitecore user manager. I’m sure this solution can be extended to search on additional user fields so please post any ideas or enhancements along with any issues you might have.

Posted in Customization, Sitecore
6 comments on “Search by Email in the Sitecore User Manager
  1. Alex says:

    In the admin bit there is part of logic missing. The way you implemented doesn’t take into account pagination. In order to enable pagination you need to override GetUsers() method and add method GetUsersByNameCount() like this:

    public override IFilterable GetUsers()
    {
    return new Filterable(new Func<int, int, System.Collections.Generic.IEnumerable>(this.GetAllUsers), new Func<System.Collections.Generic.IEnumerable>(this.GetAllUsers), new Func(this.GetUserCount), new Func<int, int, string, System.Collections.Generic.IEnumerable>(this.GetUsersByName), new Func(this.GetUsersByNameCount));
    }

    private int GetUsersByNameCount(string userNameToMatch)
    {
    int result = 0;

    …your custom logic to return correct user count here…

    return result;
    }

  2. Great, thanks for the feedback! I will update the post.

  3. Jorge Lusar says:

    Hi Scott, I have done a blog post on how to do a Full Text Search in the Sitecore User Manager http://blog.jorgelusar.me/post/full-text-search-sitecore-user-manager/

  4. Pass P says:

    It’s good solution but we can enter only 20 character in sitecore search text box. if email Id is long then how can handle it? can we increase size of character on search box?

  5. Shailesh says:

    Getting Error “Cannot read patch file ‘Domains.config’. node is missing.” for part 2

  6. Balamurguan Srinivasan says:

    Hi ,
    I need to filter users by custom userprofile field(eg. FirstName). Is that possible using search box? can you please suggest some solution to achieve this?

    Thanks,
    Balamurugan

Leave a comment