Monday, August 28, 2006

Community Server Customization - Expanded Member Search Part 2

In part 1 of this subject I explained the basics of how to change the skins and controls to allow a custom member profile field to be searched from the user/Members.aspx page. In fact, according to CS' Daily News, perhaps I said too much about various files and controls without giving an overview of what I was trying to accomplish. To summarize the intent:

  1. Additional fields have been added to the user profile in the database. This required a change to the view cs_vw_Users_FullUser in order to expose the property. My example is silly, but sufficient - Eye Color is now a profile property.
  2. We want to search by Eye Color as a distinct user property on the Members list page. We modify the search control on this page to include a drop down with various eye colors on which to search.
  3. When the search control receives the request, it must pass the selected eye color in a query string which will actually drive the database query values. In order to pass this value along to the , we added the property "EyeColor" to the UserQuery object.

With this level of change there are a number of classes which must be modified. I suggest you try very hard to work with custom versions of the classes in order to avoid upgrade problems and a later inability to find the custom code you wrote. In order to make this customization I have created the following classes in my custom assembly:

  • UserSearch - extends CS.Controls.UserSearch and implements OnInit, SearchButton_Click, GetUsersAndBindControl, and AttachChildControls mainly as copies of the original. I only call the base in OnInit after initializing a local copy of _isAdmin.
  • ExtendedUserQuery - extends CS.UserQuery and adds only the single property value I wish to store.
  • CustomCommonSqlDataProvider - extends CS.Data.SqlCommonDataProvider and overrides only the GetUsers method, along with a constructor that simply passes its parameter values to the base. Don't forget this will require a provider reference change in communityserver.config under the providers node where the name = "CommonDataProvider".

One caveat here where I broke my "customize it" rule - you would need to create a custom ForumMembersView as well in order to call the GetUsersAndBindControl on your UserSearch control because this method is not virtual. I decided to live dangerously and changed the base class to make this method virtual - but you could take the high road.

Now, I described what I did in UserSearch last time. The changes required in the common data provider will fill out the rest of the story. If the query passed into this method is of the type ExtendedUserQuery, then we will need to generate our own member query clause (if not, we can just pass the work onto the base). The member query clause is a generated sql clause executed by the stored procedure "cs_users_Get". This is a good thing because it will keep us from modifying the stored procedure. In the provided method this clause is generated by the static method BuildMemberQuery on the SqlGenerator class. We'll need to copy this entire class to our custom code, and change query parameter to accept an ExtendedUserQuery (you can't extend the class or get at its oddly protected static helpers).

We want to add our modification to the where clause, so look for the comment "// ORDER BY CLAUSE" in this method as it indicates the end of the where clause creation section where we will insert our new predicate. The where clause will have at least one predicate already, so our clause will start with " AND". The profile properties are exposed in the view mentioned above, cs_vw_Users_FullUser which has been given the alias "P". Thus, our predicate format is:

" AND P.EyeColor = '{0}'"

Finally then our new sql generator code is:

if(query.EyeColor != null)
{
    sb.AppendFormat(" AND P.EyeColor = '{0}'",query.EyeColor);
}

Once you have done this, the rest of the code in the GetUsers method remains unchanged. Finally, you should copy the static method cs_PopulateUserFromIDataReader into your provider too in order to put the custom values into the created User object before passing it back. However, it isn't necessary to get the user search results.

I said previously that it would be even better to make a more generic property search mechanism. In order to do that we change the ExtendedUserQuery to have a StringDictionary rather than an individual property. For the query string you can either ignore it and pass values via another mechanism such as user Session, or add a delimited set of strings to a Property Name value such as ppn=EyeColor.Height.Weight and a Property Value value such as ppv=Blue.74in.175. Then your sql generation code would just need to iterate the values and add predicates as necessary.

Even better would be an enhancement to CS that used a more flexible means of manipulating the query. I am biased, but I really like the WhereConstraint idea in Hydrus' DataSetToolkit technology.

Submit this story to DotNetKicks

No comments: